diff --git a/.allstar/binary_artifacts.yaml b/.allstar/binary_artifacts.yaml new file mode 100644 index 00000000..77feaba6 --- /dev/null +++ b/.allstar/binary_artifacts.yaml @@ -0,0 +1,4 @@ +# Exemption reason: Binary files are either for testing or part of the release artifacts. +# Exemption timeframe: permanent +optConfig: + optOut: true diff --git a/.github/ISSUE_TEMPLATE/--general-question.md b/.github/ISSUE_TEMPLATE/--general-question.md new file mode 100644 index 00000000..85cb3e2f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/--general-question.md @@ -0,0 +1,29 @@ +--- +name: "❓ General question" +about: Please use this template to ask general question with the External Dependency + Manager for Unity (EDM4U) +title: "[Question] " +labels: 'new, type: question' +assignees: '' + +--- + + + +### [READ] For Firebase Unity SDK question, please report to [Firebase Unity Sample](https://github.com/firebase/quickstart-unity/issues/new/choose) + +Once you've read this section and determined that your issue is appropriate for this repository, please delete this section. + +### [REQUIRED] Please fill in the following fields: + + * Unity editor version: _____ + * External Dependency Manager version: _____ + * Source you installed EDM4U: _____ (.unitypackage or Unity Package Manager) + * Features in External Dependency Manager in use: _____ (Android Resolver, iOS Resolver, VersionHandler, etc.) + * Plugins SDK in use: _____ (Firebase, Admob, Facebook, etc.) + * Platform you are using the Unity editor on: _____ (Mac, Windows, or Linux) + +### [REQUIRED] Please describe the question here: diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 00000000..e211882c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,32 @@ +--- +name: "➕ Feature request" +about: If you have a feature request for the External Dependency Manager for Unity, + file it here. +title: "[FR] " +labels: 'new, type: feature request' +assignees: '' + +--- + + + +### [READ] Guidelines + +When filing a feature request please make sure the issue title starts with "FR:". + +A good feature request ideally +* is either immediately obvious (i.e. "Add Sign in with Apple support"), or +* starts with a use case that is not achievable with the existing Firebase API and + includes an API proposal that would make the use case possible. The proposed API + change does not need to be very specific. + +Once you've read this section, please delete it and fill out the rest of the template. + +### Feature proposal + +* EDM4U Component: _____ (Android Resolver, iOS Resolver, Version Handler, Package Manager, etc) + +Describe your use case and/or feature request here. diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/issue.md new file mode 100644 index 00000000..c4bc9987 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue.md @@ -0,0 +1,38 @@ +--- +name: "\U0001F41E Bug report" +about: Please use this template to report issues with the External Dependency Manager + for Unity (EDM4U) +title: "[Bug] " +labels: 'new, type: question' + +--- + + + +### [READ] For Firebase Unity SDK issues, please report to [Firebase Unity Sample](https://github.com/firebase/quickstart-unity/issues/new/choose) + +Once you've read this section and determined that your issue is appropriate for this repository, please delete this section. + +### [REQUIRED] Please fill in the following fields: + + * Unity editor version: _____ + * External Dependency Manager version: _____ + * Source you installed EDM4U: _____ (.unitypackage or Unity Package Manager) + * Features in External Dependency Manager in use: _____ (Android Resolver, iOS Resolver, VersionHandler, etc.) + * Plugins SDK in use: _____ (Firebase, Admob, Facebook, etc.) + * Platform you are using the Unity editor on: _____ (Mac, Windows, or Linux) + +### [REQUIRED] Please describe the issue here: +(Please list the full steps to reproduce the issue. Include device logs, Unity logs, and stack traces if available.) + +#### Please answer the following, if applicable: +What's the issue repro rate? (eg 100%, 1/5 etc) + +What happened? How can we make the problem occur? +This could be a description, log/console output, etc. + +If you have a downloadable sample project that reproduces the bug you're reporting, you will +likely receive a faster response on your issue. diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 00000000..27399da4 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,90 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Workflow to build EDM4U packages and compute their hash +name: Build + +on: + schedule: + - cron: "0 10 * * *" # 10am UTC = 2am PST + + workflow_dispatch: + inputs: + unity_version: + description: 'Unity version' + default: '2019' + type: string + required: true + +env: + # Use SHA256 for hashing files. + hashCommand: "sha256sum" + +jobs: + check_and_prepare: + runs-on: ubuntu-latest + outputs: + unity_version: ${{ steps.set_outputs.outputs.unity_version }} + steps: + - id: set_outputs + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "unity_version=${{ github.event.inputs.unity_version }}" >> $GITHUB_OUTPUT + else + # inputs are not available for non "workflow_dispatch" events. Therefore, set default value here. + echo "unity_version=2019" >> $GITHUB_OUTPUT + fi + + - name: Print output + run: | + echo outputs.unity_version : ${{ steps.set_outputs.outputs.unity_version }} + + build_macos: + name: build-macos-unity${{ needs.check_and_prepare.outputs.unity_version }} + needs: [check_and_prepare] + uses: ./.github/workflows/build_macos.yaml + with: + unity_version: ${{ needs.check_and_prepare.outputs.unity_version }} + + finalizing: + # Only compute SHA hash for macOS build + name: finalizing-macOS-unity${{ needs.check_and_prepare.outputs.unity_version }} + needs: [check_and_prepare, build_macos] + runs-on: ubuntu-latest + steps: + - name: Fetch All builds + uses: actions/download-artifact@v3 + with: + path: built_artifact + + - name: Compute Plugin Hash + shell: bash + run: | + # Compute hash for .tgz package + pushd built_artifact/TarballPackage_macOS + tgz_files_list=$(find -type f -name '*.tgz') + for tgz_file in "${tgz_files_list[@]}"; do + echo tgz_file + ${{ env.hashCommand }} --tag ${tgz_file} >> edm4u_hash.txt + done + echo "::warning ::$(cat edm4u_hash.txt)" + popd + + # Compute hash for .unitypackage package + pushd built_artifact/AssetPackage_macOS + ${{ env.hashCommand }} --tag external-dependency-manager.unitypackage >> edm4u_hash.txt + echo "::warning ::$(cat edm4u_hash.txt)" + popd + + diff --git a/.github/workflows/build_macos.yaml b/.github/workflows/build_macos.yaml new file mode 100644 index 00000000..48a8b97f --- /dev/null +++ b/.github/workflows/build_macos.yaml @@ -0,0 +1,105 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Workflow to build EDM4U packages on macOS +name: Build macOS (SubWorkflow) + +on: + workflow_call: + inputs: + unity_version: + description: 'Unity version' + default: '2019' + type: string + required: true + +env: + pythonVersion: '3.7' + artifactRetentionDays: 2 + assetPackageArtifactName: "AssetPackage_macOS" + tarballPackageArtifactName: "TarballPackage_macOS" + +jobs: + build_desktop: + name: build-macOS-unity${{ inputs.unity_version}} + runs-on: macos-13 + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@v3 + + - id: build_setup + uses: ./gha/build_setup + timeout-minutes: 30 + with: + unity_version: ${{ inputs.unity_version }} + platform: macOS + python_version: ${{ env.pythonVersion }} + unity_username: ${{ secrets.UNITY_USERNAME }} + unity_password: ${{ secrets.UNITY_PASSWORD }} + unity_serial_id: ${{ secrets.SERIAL_ID }} + + - name: Set Unity Env for EDM4U build script + shell: bash + run: echo "UNITY_EXE=${{ env.UNITY_ROOT_DIR }}/Unity.app/Contents/MacOS/Unity" >> $GITHUB_ENV + + - name: Force Java 8 + shell: bash + run: echo "JAVA_HOME=${JAVA_HOME_8_X64}" >> $GITHUB_ENV + + # Build .unitypackage + - run: ./gradlew buildPlugin --info + + # Build .tgz + - run: ./gradlew buildUpmPlugin --info + + - name: Return Unity license + if: always() + uses: firebase/firebase-unity-sdk/gha/unity@main + with: + version: ${{ inputs.unity_version }} + release_license: "true" + + - name: Check build files + shell: bash + run: | + if [ -f build/external-dependency-manager.unitypackage ]; then + echo "external-dependency-manager.unitypackage zip created." + else + echo "Fail to create external-dependency-manager.unitypackage." + exit 1 + fi + if ls build/com.google.external-dependency-manager*.tgz 1> /dev/null 2>&1; then + echo "com.google.external-dependency-manager.tgz created." + else + echo "Fail to create com.google.external-dependency-manager.tgz ." + exit 1 + fi + + - name: Upload build results artifact + uses: actions/upload-artifact@v3 + if: ${{ !cancelled() }} + with: + name: ${{ env.assetPackageArtifactName }} + path: build/external-dependency-manager.unitypackage + retention-days: ${{ env.artifactRetentionDays }} + + - name: Upload build results artifact + uses: actions/upload-artifact@v3 + if: ${{ !cancelled() }} + with: + name: ${{ env.tarballPackageArtifactName }} + path: build/com.google.external-dependency-manager-*.tgz + retention-days: ${{ env.artifactRetentionDays }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..a2bff30f --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,176 @@ +name: Test + +on: + schedule: + - cron: "0 11 * * *" # 11am UTC = 3`am PST + + pull_request: + types: [ labeled, closed ] + + workflow_dispatch: + inputs: + unity_version: + description: 'Unity version (value: 2018, 2019, 2020)' + default: '2019' + required: true + include_test_types: + description: 'Specify the only types of tests to run, separated by comma. See TestTypesEnum in build.gradle for options.' + default: '' + required: false + exclude_test_types: + description: 'Specify the types of tests to exclude, separated by comma. See TestTypesEnum in build.gradle for options.' + default: '' + required: false + include_test_modules: + description: 'Specify the only modules to test against, separated by comma. See TestModulesEnum in build.gradle for options.' + default: '' + required: false + exclude_test_modules: + description: 'Specify the modules to exclude from testing against, separated by comma. See TestModulesEnum in build.gradle for options.' + default: '' + required: false + exclude_tests: + description: 'Specify the tests to exclude, separated by comma. See the tasks in build.gradle for options.' + default: '' + required: false + +env: + pythonVersion: '3.7' + artifactRetentionDays: 2 + +jobs: + check_and_prepare: + runs-on: ubuntu-latest + outputs: + unity_version: ${{ steps.set_outputs.outputs.unity_version }} + include_test_types: ${{ steps.set_outputs.outputs.include_test_types }} + exclude_test_types: ${{ steps.set_outputs.outputs.exclude_test_types }} + include_test_modules: ${{ steps.set_outputs.outputs.include_test_modules }} + exclude_test_modules: ${{ steps.set_outputs.outputs.exclude_test_modules }} + exclude_tests: ${{ steps.set_outputs.outputs.exclude_tests }} + steps: + - id: set_outputs + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "unity_version=${{ github.event.inputs.unity_version }}" >> $GITHUB_OUTPUT + echo "include_test_types=${{ github.event.inputs.include_test_types }}" >> $GITHUB_OUTPUT + echo "exclude_test_types=${{ github.event.inputs.exclude_test_types }}" >> $GITHUB_OUTPUT + echo "include_test_modules=${{ github.event.inputs.include_test_modules }}" >> $GITHUB_OUTPUT + echo "exclude_test_modules=${{ github.event.inputs.exclude_test_modules }}" >> $GITHUB_OUTPUT + echo "exclude_tests=${{ github.event.inputs.exclude_tests }}" >> $GITHUB_OUTPUT + else + # inputs are not available for non "workflow_dispatch" events. Therefore, set default value here. + echo "unity_version=2019" >> $GITHUB_OUTPUT + echo "include_test_types=" >> $GITHUB_OUTPUT + echo "exclude_test_types=" >> $GITHUB_OUTPUT + echo "include_test_modules=" >> $GITHUB_OUTPUT + echo "exclude_test_modules=" >> $GITHUB_OUTPUT + echo "exclude_tests=" >> $GITHUB_OUTPUT + + # This is currently checking for invalid trigger only. + if [[ "${{ github.event_name }}" == "schedule" ]]; then + # Do nothing for now + : + elif [[ "${{ github.event_name }}" == "pull_request" ]]; then + if [[ "${{ github.event.action }}" == "labeled" && "${{ github.event.label.name }}" == "tests-requested" ]]; then + # Do nothing for now + : + elif [[ "${{ github.event.action }}" == "closed" && "${{ github.event.pull_request.merged == true}}" == "true" ]]; then + # Do nothing for now + : + else + echo "invalid_trigger=1" >> $GITHUB_OUTPUT + fi + else + echo "invalid_trigger=1" >> $GITHUB_OUTPUT + fi + fi + + - name: Cancel workflow + if: ${{ steps.set_outputs.outputs.invalid_trigger }} + uses: andymckay/cancel-action@0.2 + + - name: Wait for workflow cancellation + if: ${{ steps.set_outputs.outputs.invalid_trigger }} + run: | + sleep 300 + exit 1 # fail out if the cancellation above somehow failed. + + - name: Print output + run: | + echo outputs.unity_version : ${{ steps.set_outputs.outputs.unity_version }} + echo outputs.include_test_types : ${{ steps.set_outputs.outputs.include_test_types }} + echo outputs.exclude_test_types : ${{ steps.set_outputs.outputs.exclude_test_types }} + echo outputs.include_test_modules : ${{ steps.set_outputs.outputs.include_test_modules }} + echo outputs.exclude_test_modules : ${{ steps.set_outputs.outputs.exclude_test_modules }} + echo outputs.exclude_tests : ${{ steps.set_outputs.outputs.exclude_tests }} + + test_on_macos: + name: test-macOS-unity${{ needs.check_and_prepare.outputs.unity_version }} + runs-on: macos-13 + needs: [check_and_prepare] + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v3 + - id: build_setup + uses: ./gha/build_setup + timeout-minutes: 30 + with: + unity_version: ${{ needs.check_and_prepare.outputs.unity_version }} + platform: macOS + python_version: ${{ env.pythonVersion }} + unity_username: ${{ secrets.UNITY_USERNAME }} + unity_password: ${{ secrets.UNITY_PASSWORD }} + unity_serial_id: ${{ secrets.SERIAL_ID }} + + - name: Set Unity Env for EDM4U build script + shell: bash + run: echo "UNITY_EXE=${{ env.UNITY_ROOT_DIR }}/Unity.app/Contents/MacOS/Unity" >> $GITHUB_ENV + + - name: Force Java 8 + shell: bash + run: echo "JAVA_HOME=${JAVA_HOME_8_X64}" >> $GITHUB_ENV + + - name: Run tests + shell: bash + timeout-minutes: 60 + run: | + ./gradlew test -q \ + -PINTERACTIVE_MODE_TESTS_ENABLED=0 \ + -PINCLUDE_TEST_TYPES="${{ needs.check_and_prepare.outputs.include_test_types }}" \ + -PEXCLUDE_TEST_TYPES="${{ needs.check_and_prepare.outputs.exclude_test_types }}" \ + -PINCLUDE_TEST_MODULES="${{ needs.check_and_prepare.outputs.include_test_modules }}" \ + -PEXCLUDE_TEST_MODULES="${{ needs.check_and_prepare.outputs.exclude_test_modules }}" \ + -PEXCLUDE_TESTS="${{ needs.check_and_prepare.outputs.exclude_tests }}" + + - name: Print test log + if: always() + shell: bash + continue-on-error: true + run: cat test_output/test*IntegrationTestsBatchMode/*.log + + - name: Obtain Failed tests from Integration tests and NUnit tests + if: always() + shell: bash + continue-on-error: true + run: | + # Quick and dirty way to get all failed tests in granular level. + # TODO: better parser for more information, ex. error message. + { cat test_output/test*/*_test.log || true; } | { grep "^Test .* FAILED$" || true; } + { cat test_output/test*/test*/results.xml || true; } | { grep '^ * static` will be added to `Podfile` by + default instead of `use_frameworks!`. This can be changed in iOS Resolver + settings. This fixes odd behaviors when pods include static libraries, ex. + Firebase Analytics. +* iOS Resolver - Added a workaround when app crashes on launch due to + `Library not loaded: @rpath/libswiftCore.dylib` when some pods includes Swift + framework. This is turned `OFF` by default and can be changed in iOS Resolver + settings. + +# Version 1.2.169 - Jan 20, 2022 +* General - Fixes #425 - Change to save `GvhProjectSettings.xml` without + Unicode byte order mark (BoM). +* Android Resolver - Remove reference to `jcenter()` +* iOS Resolver - Force setting `LANG` when executing Cocoapods in shell mode on + Mac. + +# Version 1.2.168 - Dec 9, 2021 +* All - Fixes #472 by removing the use of `System.Diagnostics.Debug.Assert` +* All - Fixed #477 by properly enabling EDM4U libraries for Unity 2021.2+ when + the package is installed through `.tgz` + +# Version 1.2.167 - Oct 6, 2021 +* All - Moved versioned `.dll` in EDM4U to a versioned folder and remove their + version postfix in their filename. For instance, `IOSResolver.dll` will be + placed at `ExternalDependencyManager/Editor/1.2.167/Google.IOSResolver.dll`. +* Android Resolver - Fixed #243 by only using the highest version in + `mainTemplate.gradle` when duplicated dependencies are presented. +* Android Resolver - Added supports to x86_64 to ABI list for Android apps on + Chrome OS. + +# Version 1.2.166 - Jun 30, 2021 +* All - Fixed #440 and fixed #447 by specifying the parameter type while calling + `GetApplicationIdentifier()` Unity API using reflection, due to a new + overloaded method introduced in Unity 2021.2. +* Android Resolver - Fixed #442 by patching `Dependency.IsGreater()` when the + version strings end '+'. + +# Version 1.2.165 - Apr 28, 2021 +## Bug Fixes +* Version Handler - Fixed #431 by replacing the use of `HttpUtility.UrlEncode()` + which causes NullReferenceException in certain version of Unity. +* Android Resolver - Check that androidSdkRootPath directory exists before using + as sdkPath. +* Android Resolver - Fixed Android Resolver integration tests with Unity + 2019.3+. + +# Version 1.2.164 - Feb 4, 2021 +## New Features +* Android Resolver - Added support for Android packages with classifier in their + namespaces. +* iOS Resolver - Added new settings in iOS Resolver to configure generated + Podfile. +* iOS Resolver - Added a new attribute `addToAllTargets` in Dependencies.xml. + +## Bug Fixes +* iOS Resolver - Fixed XML parsing for `bitcodeEnabled` attribute in + Dependencies.xml. + +# Version 1.2.163 - Dec 15, 2020 +## Bug Fixes +* Version Handler - Fixed measurement reporting + +# Version 1.2.162 - Nov 19, 2020 +## Bug Fixes +* Version Handler - Improved #413 by preventing Version Handler from running + from static constructor when it is disabled. +* Package Manager Resolver - Remove GPR + +# Version 1.2.161 - Oct 12, 2020 +## Bug Fixes +* Android Resolver - Fixed the issue that Android Resolver does not resolve + again before build in Unity 2020 if it failed to resolve previously. + +# Version 1.2.160 - Sep 30, 2020 +## Bug Fixes +* Android Resolver - Fixed a regression that gradleResolver can be null until + Initialize() is called. +* Android Resolver - Fixed a regression that Android Resolver failed in Unity + 2019.3+ due to `gradleTemplate.properties` not enabled when + `mainTemplate.gradle` is not enabled at all. + +# Version 1.2.159 - Sep 11, 2020 +## Bug Fixes +* Android Resolver - Fixed #322 where the Unity editor will lose its target SDK + setting between Unity restarts if `>28` is selected in 2019. This is due to + Unity AndroidSdkVersions enum does not contain values above 28. +* Android Resolver - Fixed #360 where building Android app with Untiy 2019.3+ + may fail due to Jetifier and AndroidX not enabled properly in generated + Gradle project. This fix requires the user to enable + `Custom Gradle Properties Template` found under + `Player Settings > Settings for Android > Publishing Settings`. + +# Version 1.2.158 - Sep 3, 2020 +## Bug Fixes +* Version Handler: Fixed editor freeze when `-executeMethod` is used in + non-batch mode. +* Android Resolver: Normalized file paths when generating local Maven repo + since the path may contains a mix of forward and backward slash on Windows. +* Export Unity Package: Fixed generation of .unitypackage with tarfile on + Windows. + +# Version 1.2.157 - Aug 6, 2020 +## Bug Fixes +* Android Resolver: Delay initialization until active build target is Android + and the editor is not in play mode. +* iOS Resolver: Delay initialization until active build target is iOS + and the editor is not in play mode. +* Export Unity Package: Workaround directory creation racy if multiple export + operations are spawned at the same time. + +# Version 1.2.156 - June 10, 2020 +## Bug Fixes +* Android Resolver: Fixed that the generated local repo assets contains + redundent labels which are causing Version Handler to failed while + uninstalling packages. +* Android Resolver: Fixed that the repo url injected into mainTemplate.gradle + is incorrect when Unity is configured to export gradle project. +* Android Resolver: Limited to only create local Maven repo when the source + repo contains ".srcaar" file. + +## Changes +* All: Described EDM4U analytics data usage in readme. + +# Version 1.2.155 - May 14, 2020 +## Bug Fixes +* All: Fixed compiler error when build with Unity 5.4 or below due to the + usage of Rect.zero. +* All: Ignore cases when checking command line arguments. + +# Version 1.2.154 - May 14, 2020 +## Bug Fixes +* All: Make each MultiSelectWindow for different purposes to have its own + unique window. + +## Changes +* All: Replace all dialog with DialogWindow which is implemented from + EditorWindow. +* Package Manager Resolver: Clarify how manifest.json will be changed in Package + Manager Resolver window. + +# Version 1.2.153 - Apr 24, 2020 +## Bug Fixes +* Android Resolver: Fixed an exception when repainting the Android resolution + window in Unity 2019.3.x. + +# Version 1.2.152 - Apr 17, 2020 +## Bug Fixes +* Version Handler: Fixed exception when waiting for enabled editor DLLs to + load. +* Android Resolver: Fixed regression when using a Custom Gradle Template + on Windows. + +# Version 1.2.151 - Apr 16, 2020 +## Bug Fixes +* Version Handler: When waiting for newly enabled editor DLLs to load, ignore + all DLLs that do not have a file-system location. +* Android Resolver: Fixed resolution when using a Custom Gradle Template with + libraries stored in a local maven repository distributed with a plugin + installed with the Unity Package Manager. + +# Version 1.2.150 - Apr 9, 2020 +## Bug Fixes +* All: The new packaging script when run on MacOS was generating a + .unitypackage archive that could not be read by Unity on Windows. + This release simply repackages the plugin with tar/gzip to fix the problem. + +# Version 1.2.149 - Apr 8, 2020 +## Bug Fixes +* Package Manager Resolver: Fixed spurious error message when resuming + migration after installing a UPM package. + +# Version 1.2.148 - Apr 8, 2020 +## Bug Fixes +* Package Manager Resolver: Fixed an exception when resuming migration + after installing a UPM package. + +# Version 1.2.147 - Apr 8, 2020 +## Bug Fixes +* Version Handler: Fixed alias traversal bug which caused problems when + migrating from installed .unitypackage files to UPM packages. + +# Version 1.2.146 - Apr 8, 2020 +## Bug Fixes +* Version Handler: Fixed exception in manifest parsing when a manifest is + detected with no aliases. + +# Version 1.2.145 - Apr 2, 2020 +## New Features +* Package Manager Resolver: Added a method to migrate Version Handler + managed packages installed via `.unitypackage` to Unity Package Manager + packages. This is initially used to migrate the External Dependency Manager + to UPM. + +## Changes +* All: Verbose logging is now no longer automatically enabled in batch mode + across all components. Instead logging can be configured using each + component's verbose logging setting or by using the `-gvh_log_debug` command + line flag when starting Unity. +* Version Handler: Sped up version handler updates when the app domain isn't + reloaded. + +## Bug Fixes +* Version Handler: Fixed the display of the obsolete files clean up dialog + when the asset database refreshes. +* Version Handler: Improved reliability of callback from + the VersionHandler.UpdateCompleteMethods event when an asset database + refresh occurs. +* Version Handler: Fixed duplicate exportPath labels when 'Assets/' is the + root of paths assigned to files. +* Version Handler: Handle empty lines in manifest files. + +# Version 1.2.144 - Mar 23, 2020 +## Changed +* iOS Resolver: Removed the ability to configure the Xcode target a Cocoapod + is added to. + +## Bug Fixes +* iOS Resolver: Reverted support for adding Cocoapods to multiple targets as + it caused a regression (exception thrown during post-build step) in some + versions of Unity. + +# Version 1.2.143 - Mar 20, 2020 +## Bug Fixes +* Android Resolver: Fixed caching of resolution state which was causing + the resolver to always run when no dependencies had changed. + +# Version 1.2.142 - Mar 19, 2020 +## Changes +* Package Manager Resolver: Enabled auto-add by default. + +# Version 1.2.141 - Mar 19, 2020 +## Bug Fixes +* Fixed a bug when retrieving project settings. If a plugin was configured + to fetch project settings, if a setting was fetched (e.g "foo") and this + setting existed in the system settings but not the project settings the + system value would override the default value leading to unexpected + behavior. +* Fixed a warning when caching web request classes in Unity 5.6. + +# Version 1.2.140 - Mar 19, 2020 +## Bug Fixes +* Fixed measurement reporting in Unity 5.x. +* Version Handler: Fixed NullReferenceException when an asset doesn't have + an AssetImporter. + +# Version 1.2.139 - Mar 18, 2020 +## Changed +* Added documentation to the built plugin. + +# Version 1.2.138 - Mar 17, 2020 +## New Features +* Package Manager Resolver: Added the Package Manager Resolver + component that allows developers to easily boostrap Unity Package Manager + (UPM) registry addition using unitypackage plugins. +* Version Handler: Added a window that allows plugins to managed by the + Version Handler to be uninstalled. +* Version Handler: Added support for displaying installed plugins. +* Version Handler: Added support for moving files in plugins to their install + locations (if the plugin has been configured to support this). +* iOS Resolver: Added the ability to configure the Xcode target a Cocoapod is + added to. + +## Bug Fixes +* Fixed upgrade from version 1.2.137 and below after the plugin rename to + EDM4U broke the upgrade process. +* Android Resolver: Worked around PlayerSettings.Android.targetSdkVersion + returning empty names for some values in 2019.x. +* Version Handler: Fixed the display of the obsolete files clean up window. +* Version Handler: Fixed managed file check when assets are modified in the + project after plugin import. + +# Version 1.2.137 - Mar 6, 2020 +## Changed +* Renamed package to External Package Manager for Unity (EDM4U). + We changed this to reflect what this plugin is doing today which is far more + than the original scope which just consisted of importing jar files from the + Android SDK maven repository. + Scripts that used to pull `play-services-resolver*.unitypackage` will now have + to request `external-dependency-manager*.unitypackage` instead. + We'll still be shipping a `play-services-resolver*_manifest.txt` file to + handle upgrading from older versions of the plugin. + +## New Features +* All Components: Added reporting of usage so that we can remotely detect + errors and target improvements. +* Android Resolver: Added support for *Dependencies.xml files in Unity Package + Manager packages. +* iOS Resolver: Added support for *Dependencies.xml files in Unity Package + Manager packages. + +## Bug Fixes +* Version Handler: Disabled attempts to disable asset metadata modification + when assets are in a Unity Package Manager managed package. + +# Version 1.2.136 - Feb 19, 2019 +## Bug Fixes +* Android Resolver: Fixed OpenJDK path discovery in Unity 2019.3.1. + +# Version 1.2.135 - Dec 5, 2019 +## Bug Fixes +* All Components: Fixed stack overflow when loading project settings. + +# Version 1.2.134 - Dec 4, 2019 +## Bug Fixes +* All Components: Fixed an issue which caused project settings to be cleared + when running in batch mode. + +# Version 1.2.133 - Nov 18, 2019 +## Bug Fixes +* All Components: Failure to save project settings will now report an error + to the log rather than throwing an exception. + +# Version 1.2.132 - Nov 11, 2019 +## Bug Fixes +* Android Resolver: Worked around expansion of DIR_UNITYPROJECT on Windows + breaking Gradle builds when used as part of a file URI. +* Android Resolver: mainTemplate.gradle is only written if it needs to be + modified. + +# Version 1.2.131 - Oct 29, 2019 +## Bug Fixes +* Version Handler: Improved execution of events on the main thread in batch + mode. +* Version Handler: Improved log level configuration at startup. +* Version Handler: Improved performance of class lookup in deferred method + calls. +* Version Handler: Fixed rename to enable / disable for editor assets. +* iOS Resolver: Improved log level configuration at startup. +* Android Resolver: Improved local maven repo path reference in + mainTemplate.gradle using DIR_UNITYPROJECT. DIR_UNITYPROJECT by Unity + to point to the local filesystem path of the Unity project when Unity + generates the Gradle project. + +# Version 1.2.130 - Oct 23, 2019 +## New Features +* iOS Resolver: Added support for modifying the Podfile before `pod install` + is executed. + +## Bug Fixes +* Version Handler: Fixed invalid classname error when calling + `VersionHandler.UpdateVersionedAssets()`. + +# Version 1.2.129 - Oct 2, 2019 +## Bug Fixes +* iOS Resolver: Changed Cocoapod integration in Unity 2019.3+ to + only add Pods to the UnityFramework target. + +# Version 1.2.128 - Oct 1, 2019 +## Bug Fixes +* iOS Resolver: Fixed Cocoapod project integration mode with Unity + 2019.3+. + +# Version 1.2.127 - Sep 30, 2019 +## Changes +* Android Resolver: All Android Resolver settings File paths are now + serialized with POSIX directory separators. + +# Version 1.2.126 - Sep 27, 2019 +## Changes +* Android Resolver: File paths are now serialized with POSIX directory + separators. +## Bug Fixes +* Android Resolver: Fixed resolution when the parent directory of a Unity + project contains a Gradle project (i.e `settings.gradle` file). + +# Version 1.2.125 - Sep 23, 2019 +## Bug Fixes +* All components: Silenced a warning about not being able to set the console + encoding to UTF8. +* Android Resolver: Worked around broken AndroidSDKTools class in some + versions of Unity. +* iOS Resolver: Fixed iOS target SDK version check +* Version Handler: Changed clean up obsolete files window so that it doesn't + exceed the screen size. + +# Version 1.2.124 - Jul 28, 2019 +## Bug Fixes +* All components: Fixed regression with source control integration when using + Unity 2019.1+. + +# Version 1.2.123 - Jul 23, 2019 +## New Features +* All components: Source control integration for project settings. +## Changes +* Android Resolver: Removed AAR cache as it now makes little difference to + incremental resolution performance. +* Android Resolver: Improved embedded resource management so that embedded + resources should upgrade when the plugin is updated without restarting + the Unity editor. +## Bug Fixes +* Version Handler: Fixed InvokeMethod() and InvokeStaticMethod() when calling + methods that have interface typed arguments. + +# Version 1.2.122 - Jul 2, 2019 +## Bug Fixes +* iOS Resolver: Worked around Unity not loading the iOS Resolver DLL as it + referenced the Xcode extension in a public interface. The iOS Resolver + DLL still references the Xcode extension internally and just handles + missing type exceptions dynamically. + +# Version 1.2.121 - Jun 27, 2019 +## Bug Fixes +* Android Resolver: Fixed warning about missing Packages folder when loading + XML dependencies files in versions of Unity without the package manager. +* Android Resolver: Fixed resolution window progress bar exceeding 100%. +* Android Resolver: If AndroidX is detected in the set of resolved libraries, + the user will be prompted to enable the Jetifier. +* Android Resolver: Improved text splitting in text area windows. +* iOS Resolver: Added support for Unity's breaking changes to the Xcode API + in 2019.3.+. Cocoapods are now added to build targets, Unity-iPhone and + UnityFramework in Unity 2019.3+. + +# Version 1.2.120 - Jun 26, 2019 +## New Features +* Android Resolver: Added support for loading *Dependencies.xml files from + Unity Package Manager packages. +* Android Resolver: Resolution window is now closed if resolution runs as + a pre-build step. +* iOS Resolver: Added support for loading *Dependencies.xml files from + Unity Package Manager packages. +## Bug Fixes +* Android Resolver: Fixed generation of relative repo paths when using + mainTemplate.gradle resolver. +* Android Resolver: Fixed copy of .srcaar to .aar files in repos embedded in a + project when a project path has characters (e.g whitespace) that are escaped + during conversion to URIs. +* Android Resolver: Fixed auto-resolution always running if the Android SDK + is managed by Unity Hub. + +# Version 1.2.119 - Jun 19, 2019 +## Bug Fixes +* Android Resolver: Fixed error reported when using Jetifier integration + in Unity 2018+ if the target SDK is set to "highest installed". + +# Version 1.2.118 - Jun 18, 2019 +## New Features +* Android Resolver: Added initial + [Jetifier](https://developer.android.com/studio/command-line/jetifier) + integration which simplifies + [migration](ttps://developer.android.com/jetpack/androidx/migrate) + to Jetpack ([AndroidX](https://developer.android.com/jetpack/androidx)) + libraries in cases where all dependencies are managed by the Android + Resolver. + This can be enabled via the `Use Jetifier` option in the + `Assets > Play Services Resolver > Android Resolver > Settings` menu. + Caveats: + - If your project contains legacy Android Support Library .jar and .aar + files, these files will need to be removed and replaced with references to + artifacts on Maven via `*Dependencies.xml` files so that the Jetifier + can map them to Jetpack (AndroidX) libraries. + For example, remove the file `support-v4-27.0.2.jar` and replace it with + `` in a + `*Dependencies.xml` file. + - If your project contains .jar or .aar files that use the legacy Android + Support Libraries, these will need to be moved into a local Maven repo + [See this guide](https://maven.apache.org/guides/mini/guide-3rd-party-jars-local.html) + and then these files should be removed from your Unity project and instead + referenced via `*Dependencies.xml` files so that the Jetifier can + patch them to reference the Jetpack lirbaries. + +## Bug Fixes +* Android Resolver: Disabled version locking of com.android.support:multidex + does not use the same versioning scheme as other legacy Android support + libraries. +* Version Handler: Made Google.VersionHandler.dll's asset GUID stable across + releases. This faciliates error-free import into projects where + Google.VersionHandler.dll is moved from the default install location. + +# Version 1.2.117 - Jun 12, 2019 +## Bug Fixes +* Android Resolver: Fix copying of .srcaar to .aar files for + mainTemplate.gradle resolution. PluginImporter configuration was previously + not being applied to .aar files unless the Unity project was saved. + +# Version 1.2.116 - Jun 7, 2019 +## Bug Fixes +* Android Resolver: Fixed resolution of Android dependencies without version + specifiers. +* Android Resolver: Fixed Maven repo not found warning in Android Resolver. +* Android Resolver: Fixed Android Player directory not found exception in + Unity 2019.x when the Android Player isn't installed. + +# Version 1.2.115 - May 28, 2019 +## Bug Fixes +* Android Resolver: Fixed exception due to Unity 2019.3.0a4 removing + x86 from the set of supported ABIs. + +# Version 1.2.114 - May 27, 2019 +## New Features +* Android Resolver: Added support for ABI stripping when using + mainTemplate.gradle. This only works with AARs stored in repos + on the local filesystem. + +# Version 1.2.113 - May 24, 2019 +## New Features +* Android Resolver: If local repos are moved, the plugin will search the + project for matching directories in an attempt to correct the error. +* Version Handler: Files can be now targeted to multiple build targets + using multiple "gvh_" asset labels. +## Bug Fixes +* Android Resolver: "implementation" or "compile" are now added correctly + to mainTemplate.gradle in Unity versions prior to 2019. + +# Version 1.2.112 - May 22, 2019 +## New Features +* Android Resolver: Added option to disable addition of dependencies to + mainTemplate.gradle. + See `Assets > Play Services Resolver > Android Resolver > Settings`. +* Android Resolver: Made paths to local maven repositories in + mainTemplate.gradle relative to the Unity project when a project is not + being exported. +## Bug Fixes +* Android Resolver: Fixed builds with mainTemplate.gradle integration in + Unity 2019. +* Android Resolver: Changed dependency inclusion in mainTemplate.gradle to + use "implementation" or "compile" depending upon the version of Gradle + included with Unity. +* Android Resolver: Gracefully handled exceptions if the console encoding + can't be modified. +* Android Resolver: Now gracefully fails if the AndroidPlayer directory + can't be found. + # Version 1.2.111 - May 9, 2019 ## Bug Fixes * Version Handler: Fixed invocation of methods with named arguments. diff --git a/LICENSE b/LICENSE index d3daa857..6258cc47 100644 --- a/LICENSE +++ b/LICENSE @@ -1,11 +1,11 @@ Copyright (C) 2014 Google Inc. - + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -213,3 +213,33 @@ Copyright (C) 2014 Google Inc. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +==================================================================================================== +This package uses MiniJSON + +Copyright (c) 2013 Calvin Rien + +Based on the JSON parser by Patrick van Bergen +http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html + +Simplified it so that it doesn't throw exceptions +and can be used in Unity iPhone with maximum code stripping. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index bf97a25f..a9aafe9f 100644 --- a/README.md +++ b/README.md @@ -1,197 +1,130 @@ -Play Services Resolver for Unity -======== +# External Dependency Manager for Unity -# Overview +[![openupm](https://img.shields.io/npm/v/com.google.external-dependency-manager?label=openupm®istry_uri=https://package.openupm.com)](https://openupm.com/packages/com.google.external-dependency-manager/) +[![openupm](https://img.shields.io/badge/dynamic/json?color=brightgreen&label=downloads&query=%24.downloads&suffix=%2Fmonth&url=https%3A%2F%2Fpackage.openupm.com%2Fdownloads%2Fpoint%2Flast-month%2Fcom.google.external-dependency-manager)](https://openupm.com/packages/com.google.external-dependency-manager/) -This library is intended to be used by any Unity plugin that requires: +## Overview - * Android specific libraries (e.g - [AARs](https://developer.android.com/studio/projects/android-library.html)). - * iOS [CocoaPods](https://cocoapods.org/). - * Version management of transitive dependencies. +The External Dependency Manager for Unity (EDM4U) (formerly Play Services +Resolver/Jar Resolver) is intended to be used by any Unity package or user that +requires: -# Background +* Android specific libraries (e.g + [AARs](https://developer.android.com/studio/projects/android-library.html)) -Many Unity plugins have dependencies upon Android specific libraries, iOS -CocoaPods, and sometimes have transitive dependencies upon other Unity plugins. -This causes the following problems: +* iOS [CocoaPods](https://cocoapods.org/) - * Integrating platform specific (e.g Android and iOS) libraries within a - Unity project can be complex and a burden on a Unity plugin maintainer. - * The process of resolving conflicting dependencies on platform specific - libraries is pushed to the developer attempting to use a Unity plugin. - The developer trying to use you plugin is very likely to give up when - faced with Android or iOS specific build errors. - * The process of resolving conflicting Unity plugins (due to shared Unity - plugin components) is pushed to the developer attempting to use your Unity - plugin. In an effort to resolve conflicts, the developer will very likely - attempt to resolve problems by deleting random files in your plugin, - report bugs when that doesn't work and finally give up. - -The Play Services Resolver plugin (the name comes from its origin of just -handling -[Google Play Services](https://developers.google.com/android/guides/overview) -dependencies on Android) provides solutions for each of these problems. - -## Android Dependency Management +* Version management of transitive dependencies -The *Android Resolver* component of this plugin will download and integrate -Android library dependencies and handle any conflicts between plugins that share -the same dependencies. +* Management of Package Manager (PM) Registries -Without the Android Resolver, typically Unity plugins bundle their AAR and -JAR dependencies, e.g. a Unity plugin `SomePlugin` that requires the Google -Play Games Android library would redistribute the library and its transitive -dependencies in the folder `SomePlugin/Android/`. When a user imports -`SomeOtherPlugin` that includes the same libraries (potentially at different -versions) in `SomeOtherPlugin/Android/`, the developer using `SomePlugin` and -`SomeOtherPlugin` will see an error when building for Android that can be hard -to interpret. +If you want to add and use iOS/Android dependencies directly in your project, +then you should to install EDM4U in your project. -Using the Android Resolver to manage Android library dependencies: +If you are a package user and the plugin you are using depends on EDM4U, *and* +the package does not include EDM4U as a package dependency already, then you +should to install EDM4U in your project. - * Solves Android library conflicts between plugins. - * Handles all of the various processing steps required to use Android - libraries (AARs, JARs) in Unity 4.x and above projects. Almost all - versions of Unity have - at best - partial support for AARs. - * (Experimental) Supports minification of included Java components without - exporting a project. +If you are a UPM package maintainer and your package requires EDM4U, then you +should add EDM4U as a +[package dependency](https://docs.unity3d.com/2019.3/Documentation/Manual/upm-dependencies.html) +in your package manifest (`package.json`): -## iOS Dependency Management +```json +{ + "dependencies": { + "com.google.external-dependency-manager": "1.2.178" + } +} +``` -The *iOS Resolver* component of this plugin integrates with -[CocoaPods](https://cocoapods.org/) to download and integrate iOS libraries -and frameworks into the Xcode project Unity generates when building for iOS. -Using CocoaPods allows multiple plugins to utilize shared components without -forcing developers to fix either duplicate or incompatible versions of -libraries included through multiple Unity plugins in their project. +You should still install EDM4U to test out the package during development. -## Unity Plugin Version Management +If you are a legacy `.unitypackage` package maintainer and your package requires +EDM4U, please ask the user to install EDM4U separately. You should install EDM4U +to test out the package during development. -Finally, the *Version Handler* component of this plugin simplifies the process -of managing transitive dependencies of Unity plugins and each plugin's upgrade -process. +Updated releases are available on +[GitHub](https://github.com/googlesamples/unity-jar-resolver) -For example, without the Version Handler plugin, if: +## Requirements - * Unity plugin `SomePlugin` includes the `Play Services Resolver` plugin at - version 1.1. - * Unity plugin `SomeOtherPlugin` includes the `Play Services Resolver` - plugin at version 1.2. +The *Android Resolver* and *iOS Resolver* components of the plugin only work +with Unity version 4.6.8 or higher. -The version of `Play Services Resolver` included in the developer's project -depends upon the order the developer imports `SomePlugin` or `SomeOtherPlugin`. +The *Version Handler* component only works with Unity 5.x or higher as it +depends upon the `PluginImporter` UnityEditor API. -This results in: +The *Package Manager Resolver* component only works with Unity 2018.4 or above, +when [scoped registry](https://docs.unity3d.com/Manual/upm-scoped.html) support +was added to the Package Manager. - * `Play Services Resolver` at version 1.2, if `SomePlugin` is imported then - `SomeOtherPlugin` is imported. - * `Play Services Resolver` at version 1.1, if `SomeOtherPlugin` is imported - then `SomePlugin` is imported. +## Getting Started -The Version Handler solves the problem of managing transitive dependencies by: +Check out [troubleshooting](troubleshooting-faq.md) if you need help. - * Specifying a set of packaging requirements that enable a plugin at - different versions to be imported into a Unity project. - * Providing activation logic that selects the latest version of a plugin - within a project. +### Install via OpenUPM -When using the Version Handler to manage `Play Services Resolver` included in -`SomePlugin` and `SomeOtherPlugin`, from the prior example, version 1.2 will -always be the version activated in a developer's Unity project. +EDM4U is available on +[OpenUPM](https://openupm.com/packages/com.google.external-dependency-manager/): -Plugin creators are encouraged to adopt this library to ease integration for -their customers. For more information about integrating Play Services Resolver -into your own plugin, see the [Plugin Redistribution](#plugin-redistribution) -section of this document. +```shell +openupm add com.google.external-dependency-manager +``` -# Requirements +### Install via git URL +1. Open Package Manager +2. Click on the + icon on the top left corner of the "Package Manager" screen +3. Click on "Install package from git url..." +4. Paste: https://github.com/googlesamples/unity-jar-resolver.git?path=upm -The Android Resolver and iOS Resolver components of the plugin only work with -Unity version 4.6.8 or higher. +### Install via Google APIs for Unity -The *Version Handler* component only works with Unity 5.x or higher as it -depends upon the `PluginImporter` UnityEditor API. +EDM4U is available both in UPM and legacy `.unitypackage` formats on +[Google APIs for Unity](https://developers.google.com/unity/archive#external_dependency_manager_for_unity). -# Getting Started +You may install the UPM version (.tgz) as a +[local UPM package](https://docs.unity3d.com/Manual/upm-ui-local.html). -Before you import the Play Services Resolver into your plugin project, you first -need to consider whether you intend to *redistribute* Play Services Resolver -along with your own plugin. +You can also install EDM4U in your project as a `.unitypackage`. This is not +recommended due to potential conflicts. -Redistributing the `Play Services Resolver` inside your own plugin will ease -the integration process for your users, by resolving dependency conflicts -between your plugin and other plugins in a user's project. +### Conflict Resolution -If you wish to redistribute the `Play Services Resolver` inside your plugin, -you **must** follow these steps when importing the -`play-services-resolver-*.unitypackage`, and when exporting your own plugin -package: +For historical reasons, a package maintainer may choose to embed EDM4U in their +package for ease of installation. This will create a conflict when you try to +install EDM4U with the steps above, or with another package with embedded EDM4U. +If your project imported a `.unitypackage` that has a copy of EDM4U embedded in +it, you may safely delete it from your Assets folder. If your project depends on +another UPM package with EDM4U, please reach out to the package maintainer and +ask them to replace it with a dependency to this package. In the meantime, you +can workaround the issue by copying the package to your Packages folder (to +create an +[embedded package](https://docs.unity3d.com/Manual/upm-concepts.html#Embedded)) +and perform the steps yourself to avoid a dependency conflict. - 1. Import the `play-services-resolver-*.unitypackage` into your plugin - project by - [running Unity from the command line](https://docs.unity3d.com/Manual/CommandLineArguments.html), ensuring that - you add the `-gvh_disable` option. - 1. Export your plugin by [running Unity from the command line](https://docs.unity3d.com/Manual/CommandLineArguments.html), ensuring that - you: - - Include the contents of the `Assets/PlayServicesResolver` directory. - - Add the `-gvh_disable` option. +### Config file -You **must** specify the `-gvh_disable` option in order for the Version -Handler to work correctly! +To start adding dependencies to your project, copy and rename the +[SampleDependencies.xml](https://github.com/googlesamples/unity-jar-resolver/blob/master/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.xml) +file into your plugin and add the dependencies your project requires. -For example, the following command will import the -`play-services-resolver-1.2.46.0.unitypackage` into the project -`MyPluginProject` and export the entire Assets folder to -`MyPlugin.unitypackage`: +The XML file needs to be under an `Editor` directory and match the name +`*Dependencies.xml`. For example, `MyPlugin/Editor/MyPluginDependencies.xml`. -``` -Unity -gvh_disable \ - -batchmode \ - -importPackage play-services-resolver-1.2.46.0.unitypackage \ - -projectPath MyPluginProject \ - -exportPackage Assets MyPlugin.unitypackage \ - -quit -``` - -## Background - -The *Version Handler* component relies upon deferring the load of editor DLLs -so that it can run first and determine the latest version of a plugin component -to activate. The build of the `Play Services Resolver` plugin has Unity asset -metadata that is configured so that the editor components are not -initially enabled when it's imported into a Unity project. To maintain this -configuration when importing the `Play Services Resolver` .unitypackage -into a Unity plugin project, you *must* specify the command line option -`-gvh_disable` which will prevent the Version Handler component from running and -changing the Unity asset metadata. +## Usages -# Android Resolver Usage +### Android Resolver The Android Resolver copies specified dependencies from local or remote Maven repositories into the Unity project when a user selects Android as the build target in the Unity editor. - 1. Add the `play-services-resolver-*.unitypackage` to your plugin - project (assuming you are developing a plugin). If you are redistributing - the Play Services Resolver with your plugin, you **must** follow the - import steps in the [Getting Started](#getting-started) section! - - 2. Copy and rename the `SampleDependencies.xml` file into your - plugin and add the dependencies your plugin requires. - - The XML file just needs to be under an `Editor` directory and match the - name `*Dependencies.xml`. For example, - `MyPlugin/Editor/MyPluginDependencies.xml`. - - 3. Follow the steps in the [Getting Started](#getting-started) - section when you are exporting your plugin package. - For example, to add the Google Play Games library -(`com.google.android.gms:play-services-games` package) at version `9.8.0` to -the set of a plugin's Android dependencies: +(`com.google.android.gms:play-services-games` package) at version `9.8.0` to the +set of a plugin's Android dependencies: -``` +```xml @@ -205,20 +138,22 @@ the set of a plugin's Android dependencies: The version specification (last component) supports: - * Specific versions e.g `9.8.0` - * Partial matches e.g `9.8.+` would match 9.8.0, 9.8.1 etc. choosing the most - recent version. - * Latest version using `LATEST` or `+`. We do *not* recommend using this - unless you're 100% sure the library you depend upon will not break your - Unity plugin in future. +* Specific versions e.g `9.8.0` + +* Partial matches e.g `9.8.+` would match 9.8.0, 9.8.1 etc. choosing the most + recent version + +* Latest version using `LATEST` or `+`. We do *not* recommend using this + unless you're 100% sure the library you depend upon will not break your + Unity plugin in future The above example specifies the dependency as a component of the Android SDK manager such that the Android SDK manager will be executed to install the -package if it's not found. If your Android dependency is located on Maven +package if it's not found. If your Android dependency is located on Maven central it's possible to specify the package simply using the `androidPackage` element: -``` +```xml @@ -226,202 +161,743 @@ element: ``` -## Auto-resolution +#### Auto-resolution By default the Android Resolver automatically monitors the dependencies you have -specified and the `Plugins/Android` folder of your Unity project. The -resolution process runs when the specified dependencies are not present in your -project. +specified and the `Plugins/Android` folder of your Unity project. The resolution +process runs when the specified dependencies are not present in your project. -The *auto-resolution* process can be disabled via the -`Assets > Play Services Resolver > Android Resolver > Settings` menu. +The *auto-resolution* process can be disabled via the `Assets > External +Dependency Manager > Android Resolver > Settings` menu. Manual resolution can be performed using the following menu options: - * `Assets > Play Services Resolver > Android Resolver > Resolve` - * `Assets > Play Services Resolver > Android Resolver > Force Resolve` +* `Assets > External Dependency Manager > Android Resolver > Resolve` + +* `Assets > External Dependency Manager > Android Resolver > Force Resolve` -## Deleting libraries +#### Deleting libraries -Resolved packages are tracked via asset labels by the Android Resolver. -They can easily be deleted using the -`Assets > Play Services Resolver > Android Resolver > Delete Resolved Libraries` -menu item. +Resolved packages are tracked via asset labels by the Android Resolver. They can +easily be deleted using the `Assets > External Dependency Manager > Android +Resolver > Delete Resolved Libraries` menu item. -## Android Manifest Variable Processing +#### Android Manifest Variable Processing Some AAR files (for example play-services-measurement) contain variables that -are processed by the Android Gradle plugin. Unfortunately, Unity does not +are processed by the Android Gradle plugin. Unfortunately, Unity does not perform the same processing when using Unity's Internal Build System, so the -Android Resolver plugin handles known cases of this variable substitution -by exploding the AAR into a folder and replacing `${applicationId}` with the +Android Resolver plugin handles known cases of this variable substitution by +exploding the AAR into a folder and replacing `${applicationId}` with the `bundleID`. Disabling AAR explosion and therefore Android manifest processing can be done -via the `Assets > Play Services Resolver > Android Resolver > Settings` menu. -You may want to disable explosion of AARs if you're exporting a project to be -built with Gradle / Android Studio. +via the `Assets > External Dependency Manager > Android Resolver > Settings` +menu. You may want to disable explosion of AARs if you're exporting a project to +be built with Gradle/Android Studio. -## ABI Stripping +#### ABI Stripping -Some AAR files contain native libraries (.so files) for each ABI supported -by Android. Unfortunately, when targeting a single ABI (e.g x86), Unity does -not strip native libraries for unused ABIs. To strip unused ABIs, the Android -Resolver plugin explodes an AAR into a folder and removes unused ABIs to -reduce the built APK size. Furthermore, if native libraries are not stripped -from an APK (e.g you have a mix of Unity's x86 library and some armeabi-v7a -libraries) Android may attempt to load the wrong library for the current -runtime ABI completely breaking your plugin when targeting some architectures. +Some AAR files contain native libraries (.so files) for each ABI supported by +Android. Unfortunately, when targeting a single ABI (e.g x86), Unity does not +strip native libraries for unused ABIs. To strip unused ABIs, the Android +Resolver plugin explodes an AAR into a folder and removes unused ABIs to reduce +the built APK size. Furthermore, if native libraries are not stripped from an +APK (e.g you have a mix of Unity's x86 library and some armeabi-v7a libraries) +Android may attempt to load the wrong library for the current runtime ABI +completely breaking your plugin when targeting some architectures. -AAR explosion and therefore ABI stripping can be disabled via the -`Assets > Play Services Resolver > Android Resolver > Settings` menu. You may -want to disable explosion of AARs if you're exporting a project to be built -with Gradle / Android Studio. +AAR explosion and therefore ABI stripping can be disabled via the `Assets > +External Dependency Manager > Android Resolver > Settings` menu. You may want to +disable explosion of AARs if you're exporting a project to be built with +Gradle/Android Studio. -## Resolution Strategies +#### Resolution Strategies By default the Android Resolver will use Gradle to download dependencies prior -to integrating them into a Unity project. This works with Unity's internal -build system and Gradle / Android Studio project export. +to integrating them into a Unity project. This works with Unity's internal build +system and Gradle/Android Studio project export. + +It's possible to change the resolution strategy via the `Assets > External +Dependency Manager > Android Resolver > Settings` menu. + +##### Download Artifacts with Gradle + +Using the default resolution strategy, the Android resolver executes the +following operations: + +- Remove the result of previous Android resolutions. E.g Delete all files and + directories labeled with "gpsr" under `Plugins/Android` from the project. -It's possible to change the resolution strategy via the -`Assets > Play Services Resolver > Android Resolver > Settings` menu. +- Collect the set of Android dependencies (libraries) specified by a project's + `*Dependencies.xml` files. -## Dependency Tracking +- Run `download_artifacts.gradle` with Gradle to resolve conflicts and, if + successful, download the set of resolved Android libraries (AARs, JARs). + +- Process each AAR/JAR so that it can be used with the currently selected + Unity build system (e.g Internal vs. Gradle, Export vs. No Export). This + involves patching each reference to `applicationId` in the + `AndroidManifest.xml` with the project's bundle ID. This means resolution + must be run again if the bundle ID has changed. + +- Move the processed AARs to `Plugins/Android` so they will be included when + Unity invokes the Android build. + +##### Integrate into mainTemplate.gradle + +Unity 5.6 introduced support for customizing the `build.gradle` used to build +Unity projects with Gradle. When the *Patch mainTemplate.gradle* setting is +enabled, rather than downloading artifacts before the build, Android resolution +results in the execution of the following operations: + +- Remove the result of previous Android resolutions. E.g Delete all files and + directories labeled with "gpsr" under `Plugins/Android` from the project and + remove sections delimited with `// Android Resolver * Start` and `// Android + Resolver * End` lines. + +- Collect the set of Android dependencies (libraries) specified by a project's + `*Dependencies.xml` files. + +- Rename any `.srcaar` files in the build to `.aar` and exclude them from + being included directly by Unity in the Android build as + `mainTemplate.gradle` will be patched to include them instead from their + local maven repositories. + +- Inject the required Gradle repositories into `mainTemplate.gradle` at the + line matching the pattern `.*apply plugin: + 'com\.android\.(application|library)'.*` or the section starting at the line + `// Android Resolver Repos Start`. If you want to control the injection + point in the file, the section delimited by the lines `// Android Resolver + Repos Start` and `// Android Resolver Repos End` should be placed in the + global scope before the `dependencies` section. + +- Inject the required Android dependencies (libraries) into + `mainTemplate.gradle` at the line matching the pattern `***DEPS***` or the + section starting at the line `// Android Resolver Dependencies Start`. If + you want to control the injection point in the file, the section delimited + by the lines `// Android Resolver Dependencies Start` and `// Android + Resolver Dependencies End` should be placed in the `dependencies` section. + +- Inject the packaging options logic, which excludes architecture specific + libraries based upon the selected build target, into `mainTemplate.gradle` + at the line matching the pattern `android +{` or the section starting at the + line `// Android Resolver Exclusions Start`. If you want to control the + injection point in the file, the section delimited by the lines `// Android + Resolver Exclusions Start` and `// Android Resolver Exclusions End` should + be placed in the global scope before the `android` section. + +#### Dependency Tracking The Android Resolver creates the `ProjectSettings/AndroidResolverDependencies.xml` to quickly determine the set -of resolved dependencies in a project. This is used by the auto-resolution +of resolved dependencies in a project. This is used by the auto-resolution process to only run the expensive resolution process when necessary. -## Displaying Dependencies +#### Displaying Dependencies -It's possible to display the set of dependencies the Android Resolver -would download and process in your project via the -`Assets > Play Services Resolver > Android Resolver > Display Libraries` menu -item. +It's possible to display the set of dependencies the Android Resolver would +download and process in your project via the `Assets > External Dependency +Manager > Android Resolver > Display Libraries` menu item. -# iOS Resolver Usage +### iOS Resolver The iOS resolver component of this plugin manages -[CocoaPods](https://cocoapods.org/). A CocoaPods `Podfile` is generated and -the `pod` tool is executed as a post build process step to add dependencies -to the Xcode project exported by Unity. +[CocoaPods](https://cocoapods.org/). A CocoaPods `Podfile` is generated and the +`pod` tool is executed as a post build process step to add dependencies to the +Xcode project exported by Unity. Dependencies for iOS are added by referring to CocoaPods. - 1. Add the `play-services-resolver-*.unitypackage` to your plugin - project (assuming you are developing a plugin). If you are redistributing - the Play Services Resolver with your plugin, you **must** follow the - import steps in the [Getting Started](#getting-started) section! - - 2. Copy and rename the SampleDependencies.xml file into your - plugin and add the dependencies your plugin requires. - - The XML file just needs to be under an `Editor` directory and match the - name `*Dependencies.xml`. For example, - `MyPlugin/Editor/MyPluginDependencies.xml`. - - 3. Follow the steps in the [Getting Started](#getting-started) - section when you are exporting your plugin package. - For example, to add the AdMob pod, version 7.0 or greater with bitcode enabled: -``` +```xml + minTargetSdk="6.0" addToAllTargets="false" /> ``` -## Integration Strategies +#### Integration Strategies The `CocoaPods` are either: - * Downloaded and injected into the Xcode project file directly, rather than - creating a separate xcworkspace. We call this `Xcode project` integration. - * If the Unity version supports opening a xcworkspace file, the `pod` tool - is used as intended to generate a xcworkspace which references the - CocoaPods. We call this `Xcode workspace` integration. -The resolution strategy can be changed via the -`Assets > Play Services Resolver > iOS Resolver > Settings` menu. +* Downloaded and injected into the Xcode project file directly, rather than + creating a separate xcworkspace. We call this `Xcode project` integration. + +* If the Unity version supports opening a xcworkspace file, the `pod` tool is + used as intended to generate a xcworkspace which references the CocoaPods. + We call this `Xcode workspace` integration. + +The resolution strategy can be changed via the `Assets > External Dependency +Manager > iOS Resolver > Settings` menu. + +##### Appending text to generated Podfile + +In order to modify the generated Podfile you can create a script like this: + +```csharp +using System.IO; + +using UnityEditor; +using UnityEditor.Callbacks; +using UnityEngine; + +public class PostProcessIOS : MonoBehaviour +{ + // Must be between 40 and 50 to ensure that it's not overriden by Podfile generation (40) and + // that it's added before "pod install" (50). + [PostProcessBuildAttribute(45)] + private static void PostProcessBuild_iOS(BuildTarget target, string buildPath) + { + if (target == BuildTarget.iOS) + { + using (StreamWriter sw = File.AppendText(buildPath + "/Podfile")) + { + // E.g. add an app extension + sw.WriteLine("\ntarget 'NSExtension' do\n pod 'Firebase/Messaging', '6.6.0'\nend"); + } + } + } +} +``` + +### Package Manager Resolver + +Adding registries to the +[Package Manager](https://docs.unity3d.com/Manual/Packages.html) (PM) is a +manual process. The Package Manager Resolver (PMR) component of this plugin +makes it easy for plugin maintainers to distribute new PM registry servers and +easy for plugin users to manage PM registry servers. + +#### Adding Registries + +For example, to add a registry for plugins in the scope `com.coolstuff`: + +```xml + + + + com.coolstuff + + + +``` + +When PMR is loaded it will prompt the developer to add the registry to their +project if it isn't already present in the `Packages/manifest.json` file. + +For more information, see Unity's documentation on +[scoped package registries](https://docs.unity3d.com/Manual/upm-scoped.html). + +#### Managing Registries + +It's possible to add and remove registries that are specified via PMR XML +configuration files via the following menu options: + +* `Assets > External Dependency Manager > Package Manager Resolver > Add + Registries` will prompt the user with a window which allows them to add + registries discovered in the project to the Package Manager. + +* `Assets > External Dependency Manager > Package Manager Resolver > Remove + Registries` will prompt the user with a window which allows them to remove + registries discovered in the project from the Package Manager. -# Version Handler Usage +* `Assets > External Dependency Manager > Package Manager Resolver > Modify + Registries` will prompt the user with a window which allows them to add or + remove registries discovered in the project. + +#### Migration + +PMR can migrate Version Handler packages installed in the `Assets` folder to PM +packages. This requires the plugins to implement the following: + +* `.unitypackage` must include a Version Handler manifests that describes the + components of the plugin. If the plugin has no dependencies the manifest + would just include the files in the plugin. + +* The PM package JSON provided by the registry must include a keyword (in the + `versions.VERSION.keyword` list) that maps the PM package to a Version + Handler package using the format `vh-name:VERSION_HANDLER_MANIFEST_NAME` + where `VERSION_HANDLER_MANIFEST_NAME` is the name of the manifest defined in + the `.unitypackage`. For more information see the description of the + `gvhp_manifestname` asset label in the [Version Handler](#version-handler) + section. + +When using the `Assets > External Dependency Manager > Package Manager +Resolver > Migrate Packages` menu option, PMR then will: + +* List all Version Handler manager packages in the project. + +* Search all available packages in the PM registries and fetch keywords + associated with each package parsing the Version Handler manifest names for + each package. + +* Map each installed Version Handler package to a PM package. + +* Prompt the user to migrate the discovered packages. + +* Perform package migration for all selected packages if the user clicks the + `Apply` button. + +#### Configuration + +PMR can be configured via the `Assets > External Dependency Manager > Package +Manager Resolver > Settings` menu option: + +* `Add package registries` when enabled, when the plugin loads or registry + configuration files change, this will prompt the user to add registries that + are not present in the Package Manager. + +* `Prompt to add package registries` will cause a developer to be prompted + with a window that will ask for confirmation before adding registries. When + this is disabled registries are added silently to the project. + +* `Prompt to migrate packages` will cause a developer to be prompted with a + window that will ask for confirmation before migrating packages installed in + the `Assets` directory to PM packages. + +* `Enable Analytics Reporting` when enabled, reports the use of the plugin to + the developers so they can make imrpovements. + +* `Verbose logging` when enabled prints debug information to the console which + can be useful when filing bug reports. + +### Version Handler The Version Handler component of this plugin manages: -* Shared Unity plugin dependencies. -* Upgrading Unity plugins by cleaning up old files from previous versions. + +* Shared Unity plugin dependencies. + +* Upgrading Unity plugins by cleaning up old files from previous versions. + +* Uninstallation of plugins that are distributed with manifest files. + +* Restoration of plugin assets to their original install locations if assets + are tagged with the `exportpath` label. + +Since the Version Handler needs to modify Unity asset metadata (`.meta` files), +to enable/disable components, rename and delete asset files it does not work +with Package Manager installed packages. It's still possible to include EDM4U in +Package Manager packages, the Version Handler component simply won't do anything +to PM plugins in this case. + +#### Using Version Handler Managed Plugins + +If a plugin is imported at multiple different versions into a project, if the +Version Handler is enabled, it will automatically check all managed assets to +determine the set of assets that are out of date and assets that should be +removed. To disable automatic checking managed assets disable the `Enable +version management` option in the `Assets > External Dependency Manager > +Version Handler > Settings` menu. + +If version management is disabled, it's possible to check managed assets +manually using the `Assets > External Dependency Manager > Version Handler > +Update` menu option. + +##### Listing Managed Plugins + +Plugins managed by the Version Handler, those that ship with manifest files, can +displayed using the `Assets > External Dependency Manager > Version Handler > +Display Managed Packages` menu option. The list of plugins are written to the +console window along with the set of files used by each plugin. + +##### Uninstalling Managed Plugins + +Plugins managed by the Version Handler, those that ship with manifest files, can +be removed using the `Assets > External Dependency Manager > Version Handler > +Uninstall Managed Packages` menu option. This operation will display a window +that allows a developer to select a set of plugins to remove which will remove +all files owned by each plugin excluding those that are in use by other +installed plugins. + +Files managed by the Version Handler, those labeled with the `gvh` asset label, +can be checked to see whether anything needs to be upgraded, disabled or removed +using the `Assets > External Dependency Manager > Version Handler > Update` menu +option. + +##### Restore Install Paths + +Some developers move assets around in their project which can make it harder for +plugin maintainers to debug issues if this breaks Unity's +[special folders](https://docs.unity3d.com/Manual/SpecialFolders.html) rules. If +assets are labeled with their original install/export path (see +`gvhp_exportpath` below), Version Handler can restore assets to their original +locations when using the `Assets > External Dependency Manager > Version +Handler > Move Files To Install Locations` menu option. + +##### Settings + +Some behavior of the Version Handler can be configured via the `Assets > +External Dependency Manager > Version Handler > Settings` menu option. + +* `Enable version management` controls whether the plugin should automatically + check asset versions and apply changes. If this is disabled the process + should be run manually when installing or upgrading managed plugins using + `Assets > External Dependency Manager > Version Handler > Update`. + +* `Rename to canonical filenames` is a legacy option that will rename files to + remove version numbers and other labels from filenames. + +* `Prompt for obsolete file deletion` enables the display of a window when + obsolete files are deleted allowing the developer to select which files to + delete and those to keep. + +* `Allow disabling files via renaming` controls whether obsolete or disabled + files should be disabled by renaming them to `myfilename_DISABLED`. Renaming + to disable files is required in some scenarios where Unity doesn't support + removing files from the build via the PluginImporter. + +* `Enable Analytics Reporting` enables/disables usage reporting to plugin + developers to improve the product. + +* `Verbose logging` enables *very* noisy log output that is useful for + debugging while filing a bug report or building a new managed plugin. + +* `Use project settings` saves settings for the plugin in the project rather + than system-wide. + +#### Redistributing a Managed Plugin + +The Version Handler employs a couple of methods for managing version selection, +upgrade and removal of plugins. + +* Each plugin can ship with a manifest file that lists the files it includes. + This makes it possible for Version Handler to calculate the difference in + assets between the most recent release of a plugin and the previous release + installed in a project. If a files are removed the Version Handler will + prompt the user to clean up obsolete files. + +* Plugins can ship using assets with unique names, unique GUIDs and version + number labels. Version numbers can be attached to assets using labels or + added to the filename (e.g `myfile.txt` would be `myfile_version-x.y.z.txt). + This allows the Version Handler to determine which set of files are the same + file at different versions, select the most recent version and prompt the + developer to clean up old versions. Unity plugins can be managed by the Version Handler using the following steps: - 1. Add the `gvh` asset label to each asset (file) you want Version Handler - to manage. - 1. Add the `gvh_version-VERSION` label to each asset where `VERSION` is the - version of the plugin you're releasing (e.g 1.2.3). - 1. Optional: Add `gvh_targets-editor` label to each editor DLL in your - plugin and disable `editor` as a target platform for the DLL. - The Version Handler will enable the most recent version of this DLL when - the plugin is imported. - 1. Optional: If your plugin is included in other Unity plugins, you should - add the version number to each filename and change the GUID of each asset. - This allows multiple versions of your plugin to be imported into a Unity - project, with the Version Handler component activating only the most - recent version. - 1. Create a manifest text file named `MY_UNIQUE_PLUGIN_NAME_VERSION.txt` - that lists all the files in your plugin relative to the project root. - Then add the `gvh_manifest` label to the asset to indicate this file is - a plugin manifest. - 1. Redistribute the `Play Services Resolver` Unity plugin with your plugin. - See the [Plugin Redistribution](#plugin-redistribution) for the details. +1. Add the `gvh` asset label to each asset (file) you want Version Handler to + manage. + +1. Add the `gvh_version-VERSION` label to each asset where `VERSION` is the + version of the plugin you're releasing (e.g 1.2.3). + +1. Add the `gvhp_exportpath-PATH` label to each asset where `PATH` is the + export path of the file when the `.unitypackage` is created. This is used to + track files if they're moved around in a project by developers. + +1. Optional: Add `gvh_targets-editor` label to each editor DLL in your plugin + and disable `editor` as a target platform for the DLL. The Version Handler + will enable the most recent version of this DLL when the plugin is imported. + +1. Optional: If your plugin is included in other Unity plugins, you should add + the version number to each filename and change the GUID of each asset. This + allows multiple versions of your plugin to be imported into a Unity project, + with the Version Handler component activating only the most recent version. + +1. Create a manifest text file named `MY_UNIQUE_PLUGIN_NAME_VERSION.txt` that + lists all the files in your plugin relative to the project root. Then add + the `gvh_manifest` label to the asset to indicate this file is a plugin + manifest. + +1. Optional: Add a `gvhp_manifestname-NAME` label to your manifest file to + provide a human readable name for your package. If this isn't provided the + name of the manifest file will be used as the package name. NAME can match + the pattern `[0-9]+[a-zA-Z -]` where a leading integer will set the priority + of the name where `0` is the highest priority and preferably used as the + display name. The lowest value (i.e highest priority name) will be used as + the display name and all other specified names will be aliases of the + display name. Aliases can refer to previous names of the package allowing + renaming across published versions. + +1. Redistribute EDM4U Unity plugin with your plugin. See the + [Plugin Redistribution](#plugin-redistribution) section for details. If you follow these steps: - * When users import a newer version of your plugin, files referenced by the - older version's manifest are cleaned up. - * The latest version of the plugin will be selected when users import - multiple packages that include your plugin, assuming the steps in - [Plugin Redistribution](#plugin-redistribution) are followed. +* When users import a newer version of your plugin, files referenced by the + older version's manifest are cleaned up. -## Building from Source +* The latest version of the plugin will be selected when users import multiple + packages that include your plugin, assuming the steps in + [Plugin Redistribution](#plugin-redistribution) are followed. -To build this plugin from source you need the following tools installed: - * Unity (with iOS and Android modules installed) +## Background -You can build the plugin by running the following from your shell -(Linux / OSX): +Many Unity plugins have dependencies upon Android specific libraries, iOS +CocoaPods, and sometimes have transitive dependencies upon other Unity plugins. +This causes the following problems: + +* Integrating platform specific (e.g Android and iOS) libraries within a Unity + project can be complex and a burden on a Unity plugin maintainer. +* The process of resolving conflicting dependencies on platform specific + libraries is pushed to the developer attempting to use a Unity plugin. The + developer trying to use your plugin is very likely to give up when faced + with Android or iOS specific build errors. +* The process of resolving conflicting Unity plugins (due to shared Unity + plugin components) is pushed to the developer attempting to use your Unity + plugin. In an effort to resolve conflicts, the developer will very likely + attempt to resolve problems by deleting random files in your plugin, report + bugs when that doesn't work and finally give up. + +EDM4U provides solutions for each of these problems. + +### Android Dependency Management + +The *Android Resolver* component of this plugin will download and integrate +Android library dependencies and handle any conflicts between plugins that share +the same dependencies. + +Without the Android Resolver, typically Unity plugins bundle their AAR and JAR +dependencies, e.g. a Unity plugin `SomePlugin` that requires the Google Play +Games Android library would redistribute the library and its transitive +dependencies in the folder `SomePlugin/Android/`. When a user imports +`SomeOtherPlugin` that includes the same libraries (potentially at different +versions) in `SomeOtherPlugin/Android/`, the developer using `SomePlugin` and +`SomeOtherPlugin` will see an error when building for Android that can be hard +to interpret. + +Using the Android Resolver to manage Android library dependencies: + +* Solves Android library conflicts between plugins. +* Handles all of the various processing steps required to use Android + libraries (AARs, JARs) in Unity 4.x and above projects. Almost all versions + of Unity have - at best - partial support for AARs. +* (Experimental) Supports minification of included Java components without + exporting a project. + +### iOS Dependency Management + +The *iOS Resolver* component of this plugin integrates with +[CocoaPods](https://cocoapods.org/) to download and integrate iOS libraries and +frameworks into the Xcode project Unity generates when building for iOS. Using +CocoaPods allows multiple plugins to utilize shared components without forcing +developers to fix either duplicate or incompatible versions of libraries +included through multiple Unity plugins in their project. +### Package Manager Registry Setup + +The [Package Manager](https://docs.unity3d.com/Manual/Packages.html) (PM) makes +use of [NPM](https://www.npmjs.com/) registry servers for package hosting and +provides ways to discover, install, upgrade and uninstall packages. This makes +it easier for developers to manage plugins within their projects. + +However, installing additional package registries requires a few manual steps +that can potentially be error prone. The *Package Manager Resolver* component of +this plugin integrates with [PM](https://docs.unity3d.com/Manual/Packages.html) +to provide a way to auto-install PM package registries when a `.unitypackage` is +installed which allows plugin maintainers to ship a `.unitypackage` that can +provide access to their own PM registry server to make it easier for developers +to manage their plugins. + +### Unity Plugin Version Management + +Finally, the *Version Handler* component of this plugin simplifies the process +of managing transitive dependencies of Unity plugins and each plugin's upgrade +process. + +For example, without the Version Handler plugin, if: + +* Unity plugin `SomePlugin` includes `EDM4U` plugin at version 1.1. +* Unity plugin `SomeOtherPlugin` includes `EDM4U` plugin at version 1.2. + +The version of `EDM4U` included in the developer's project depends upon the +order the developer imports `SomePlugin` or `SomeOtherPlugin`. + +This results in: + +* `EDM4U` at version 1.2, if `SomePlugin` is imported then `SomeOtherPlugin` + is imported. +* `EDM4U` at version 1.1, if `SomeOtherPlugin` is imported then `SomePlugin` + is imported. + +The Version Handler solves the problem of managing transitive dependencies by: + +* Specifying a set of packaging requirements that enable a plugin at different + versions to be imported into a Unity project. +* Providing activation logic that selects the latest version of a plugin + within a project. + +When using the Version Handler to manage `EDM4U` included in `SomePlugin` and +`SomeOtherPlugin`, from the prior example, version 1.2 will always be the +version activated in a developer's Unity project. + +Plugin creators are encouraged to adopt this library to ease integration for +their customers. For more information about integrating EDM4U into your own +plugin, see the [Plugin Redistribution](#plugin-redistribution) section of this +document. + +## Analytics + +The External Dependency Manager for Unity plugin by default logs usage to Google +Analytics. The purpose of the logging is to quantitatively measure the usage of +functionality, to gather reports on integration failures and to inform future +improvements to the developer experience of the External Dependency Manager +plugin. Note that the analytics collected are limited to the scope of the EDM4U +plugin’s usage. + +For details of what is logged, please refer to the usage of +`EditorMeasurement.Report()` in the source code. + +## Plugin Redistribution + +If you are a package maintainer and your package depends on EDM4U, it is highly +recommended to use the UPM format and add EDM4U as a dependency. If you must +include it in your `.unitypackage`, redistributing `EDM4U` inside your own +plugin might ease the integration process for your users. + +If you wish to redistribute `EDM4U` inside your plugin, you **must** follow +these steps when importing the `external-dependency-manager-*.unitypackage`, and +when exporting your own plugin package: + +1. Import the `external-dependency-manager-*.unitypackage` into your plugin + project by + [running Unity from the command line](https://docs.unity3d.com/Manual/CommandLineArguments.html), + ensuring that you add the `-gvh_disable` option. +1. Export your plugin by + [running Unity from the command line](https://docs.unity3d.com/Manual/CommandLineArguments.html), + ensuring that you: + - Include the contents of the `Assets/PlayServicesResolver` and + `Assets/ExternalDependencyManager` directory. + - Add the `-gvh_disable` option. + +You **must** specify the `-gvh_disable` option in order for the Version Handler +to work correctly! + +For example, the following command will import the +`external-dependency-manager-1.2.46.0.unitypackage` into the project +`MyPluginProject` and export the entire Assets folder to +`MyPlugin.unitypackage`: + +```shell +Unity -gvh_disable \ + -batchmode \ + -importPackage external-dependency-manager-1.2.46.0.unitypackage \ + -projectPath MyPluginProject \ + -exportPackage Assets MyPlugin.unitypackage \ + -quit ``` + +### Background + +The *Version Handler* component relies upon deferring the load of editor DLLs so +that it can run first and determine the latest version of a plugin component to +activate. The build of `EDM4U` plugin has Unity asset metadata that is +configured so that the editor components are not initially enabled when it's +imported into a Unity project. To maintain this configuration when importing the +`external-dependency-manager.unitypackage` into a Unity plugin project, you +*must* specify the command line option `-gvh_disable` which will prevent the +Version Handler component from running and changing the Unity asset metadata. + +## Building from Source + +To build this plugin from source you need the following tools installed: * Unity +2021 and below (with iOS and Android modules installed) * Java 11 + +You can build the plugin by running the following from your shell (Linux / OSX): + +```shell ./gradlew build + ``` or Windows: -``` +```shell ./gradlew.bat build ``` -### Releasing - -Each time a new build of this plugin is checked into the source tree you -need to do the following: - - * Bump the plugin version variable `pluginVersion` in `build.gradle` - * Update `CHANGELOG.md` with the new version number and changes included in - the release. - * Build the release using `./gradle release` which performs the following: - * Updates `play-services-resolver-*.unitypackage` - * Copies the unpacked plugin to the `exploded` directory. - * Updates template metadata files in the `plugin` directory. - The GUIDs of all asset metadata is modified due to the version number - change. Each file within the plugin is versioned to allow multiple - versions of the plugin to be imported into a Unity project which allows - the most recent version to be activated by the Version Handler - component. - * Create the release commit and tag the release using - `./gradle gitTagRelease` which performs the following: - * `git add -A` to pick up all modified, new and deleted files in the tree. - * `git commit --amend -a` to create a release commit with the release notes - in the change log. - * `git tag -a RELEASE -m "version RELEASE"` to tag the release. +If Java 11 is not your default Java command, add +`-Dorg.gradle.java.home=` to the command above. + +## Testing + +You can run the tests by running the following from your shell (Linux / OSX): + +```shell +./gradlew test +``` + +or Windows: + +```shell +./gradlew.bat test +``` + +The following properties can be set to narrow down the tests to run or change +the test run behavior. + +* `INTERACTIVE_MODE_TESTS_ENABLED` - Default to `1`. Set to `1` to enable + interactive mode tests, which requires GPU on the machine. Otherwise, only + run tests in the batch mode. +* `INCLUDE_TEST_TYPES` - Default to empty string, which means to include every + type of the test. To narrow down the types of test to run, set this + properties with a list of case-insensitive type strings separated by comma. + For instance, `-PINCLUDE_TEST_TYPES="Python,NUnit"` means to include only + Python tests and NUnit tests. See `TestTypeEnum` in `build.gradle` for + available options. +* `EXCLUDE_TEST_TYPES` - Default to empty string, which means to exclude none. + To add types of tests to exclude, set this properties with a list of + case-insensitive type strings separated by comma. For instance, + `-PEXCLUDE_TEST_TYPES="Python,NUnit"` means to exclude Python tests and + NUnit tests. See `TestTypeEnum` in `build.gradle` for available options. +* `INCLUDE_TEST_MODULES` - Default to empty string, which means to include the + tests for every modules. To narrow down modules to test, set this properties + with a list of case-insensitive module strings separated by comma. For + instance, `-PINCLUDE_TEST_MODULES="Tool,AndroidResolver"` means to run tests + for tools and Android Resolver only. See `TestModuleEnum` in `build.gradle` + for available options. +* `EXCLUDE_TEST_MODULES` - Default to empty string, which means to exclude + none. To add modules to exclude, set this properties with a list of + case-insensitive module strings separated by comma. For instance, + `-PEXCLUDE_TEST_MODULES="Tool,AndroidResolver"` means to run tests for any + modules other than tools and Android Resolver. See `TestModuleEnum` in + `build.gradle` for available options. +* `EXCLUDE_TESTS` - Default to empty string, which means to exclude none. To + add tests to exclude, set this properties with a list of case-insensitive + test names separated by comma. For instance, + `-PEXCLUDE_TESTS="testGenGuids,testDownloadArtifacts"` means to run tests + except the tests with name of `testGenGuids` and `testDownloadArtifacts`. +* `CONTINUE_ON_FAIL_FOR_TESTS_ENABLED` - Default to `1`. Set to `1` to + continue running the next test when the current one fails. Otherwise, the + build script stops whenever any test fails. + +For instance, by running the following command, it only runs the Unity +integration tests that does not requires GPU, but exclude tests for Android +Resolver module and iOS Resolver module. + +```shell +./gradlew test \ + -PINTERACTIVE_MODE_TESTS_ENABLED=0 \ + -PINCLUDE_TEST_TYPES="Integration" \ + -PEXCLUDE_TEST_MODULES="AndroidResolver,iOSResolver" +``` + +## Releasing + +Each time a new build of this plugin is checked into the source tree you need to +do the following: + +* Bump the plugin version variable `pluginVersion` in `build.gradle` +* Update `CHANGELOG.md` with the new version number and changes included in + the release. +* Build the release using `./gradlew release` which performs the following: + * Updates `external-dependency-manager-*.unitypackage` + * Copies the unpacked plugin to the `exploded` directory. + * Updates template metadata files in the `plugin` directory. The GUIDs of + all asset metadata is modified due to the version number change. Each + file within the plugin is versioned to allow multiple versions of the + plugin to be imported into a Unity project which allows the most recent + version to be activated by the Version Handler component. +* Create release commit using `./gradlew gitCreateReleaseCommit` which + performs `git commit -a -m "description from CHANGELOG.md"` +* Once the release commit is merge, tag the release using `./gradlew + gitTagRelease` which performs the following: + * `git tag -a pluginVersion -m "version RELEASE"` to tag the release. +* Update tags on remote branch using `git push --tag REMOTE HEAD:master` diff --git a/build.gradle b/build.gradle index b34c5ad5..cb824eac 100644 --- a/build.gradle +++ b/build.gradle @@ -2,10 +2,14 @@ * Gradle file to build the Jar Resolver Unity plugin. */ buildscript { - repositories { - jcenter() - mavenLocal() - } + repositories { + mavenCentral() + mavenLocal() + } +} + +plugins { + id "com.jetbrains.python.envs" version "0.0.30" } /* @@ -20,8 +24,7 @@ project.ext { String unitySearchDirExcludesString = findProperty("UNITY_EXCLUDES") String[] unitySearchDirExcludes = unitySearchDirExcludesString ? - unitySearchDirExcludesString.tokenize(";") : - ["**/PackageManager/Editor"] + unitySearchDirExcludesString.tokenize(";") : [] // Save the current OS. operatingSystem = OperatingSystem.getOperatingSystem() @@ -32,7 +35,7 @@ project.ext { (OperatingSystem.MAC_OSX): ["/Applications/Unity/Unity.app/Contents/MacOS/Unity"] + (new FileNameFinder()).getFileNames( - "/", "Applications/UnityHub/*/Unity.app/Contents/MacOS/Unity"), + "/", "Applications/Unity/Hub/Editor/*/Unity.app/Contents/MacOS/Unity"), (OperatingSystem.WINDOWS): ["\\Program Files\\Unity\\Editor\\Unity.exe"] + (new FileNameFinder()).getFileNames( @@ -109,7 +112,7 @@ project.ext { "XBUILD_EXE", false, { unityRootDirTree.matching { include (operatingSystem == OperatingSystem.WINDOWS ? - "**/Mono/bin/xbuild.bat" : "**/xbuild") + "**/bin/xbuild.bat" : "**/xbuild") exclude unitySearchDirExcludes } }) @@ -118,38 +121,57 @@ project.ext { } saveProperty("XBUILD_EXE", xbuildExe, cacheProperties) - // Find the NUnit framework dll. - unityNUnitDll = getFileFromPropertyOrFileTree( - "UNITY_NUNIT_PATH", true, { + // Find mono to determine the distribution being used. + monoExe = getFileFromPropertyOrFileTree( + "MONO_EXE", false, { unityRootDirTree.matching { - include "**/nunit.framework.dll" + include (operatingSystem == OperatingSystem.WINDOWS ? + "**/bin/mono.bat" : "**/bin/mono") exclude unitySearchDirExcludes } }) - if (unityNUnitDll == null) { - logger.warn("Unity NUnit DLL not found, tests will not build.") - } - saveProperty("UNITY_NUNIT_PATH", unityNUnitDll, cacheProperties) + saveProperty("MONO_EXE", monoExe, cacheProperties) - // nunit-console is used to run tests. - nunitConsoleExe = getFileFromPropertyOrFileTree( - "NUNIT_CONSOLE_EXE", false, { - unityRootDirTree.matching { - include (operatingSystem == OperatingSystem.WINDOWS ? - "**/Mono/bin/nunit-console2.bat" : - "**/nunit-console2") - exclude unitySearchDirExcludes + // Get the mono distribution version. + def versionRegEx = /^.* version ([^ ]+) .*/ + def stdout = new ByteArrayOutputStream() + exec { + commandLine monoExe, "-V" + ignoreExitValue true + standardOutput = stdout + } + def monoVersionList = + stdout.toString().replace("\r\n", "\n").tokenize("\n").findResults { + def versionMatch = it =~ versionRegEx + if (versionMatch.matches()) { + return versionMatch.group(1) } - }) - if (nunitConsoleExe == null) { - logger.warn("nunit_console not found (NUNIT_CONSOLE_EXE) C# unit test " + - "execution will fail.") + return null + } + if (!monoVersionList) { + throw new StopActionException( + sprintf("Unable to determine mono version from %s", monoExe)) + } + monoVersion = monoVersionList[0] + + // Mono 5.x and above generate .pdb files that are compatible with visual + // studio as opposed to the mono-specific .pdb files. + pdbSupported = monoVersion.tokenize(".")[0].toInteger() >= 5 + + if (pdbSupported) { + logger.warn( + sprintf("Mono %s detected which will generate .pdb files " + + "that are not compatible with older versions of Unity. " + + "This can be fixed by compiling with Unity 5.6.", + monoVersion)) } - saveProperty("NUNIT_CONSOLE_EXE", nunitConsoleExe, cacheProperties) + + // Get the directory that contains this script. + scriptDirectory = buildscript.sourceFile.getParentFile() // It can take a while to search for build tools, so cache paths in the project // properties. - File projectPropertiesFile = new File("gradle.properties") + File projectPropertiesFile = new File(scriptDirectory,"gradle.properties") if (!projectPropertiesFile.exists()) { logger.info(sprintf("Saving %s to %s", cacheProperties.stringPropertyNames(), @@ -157,16 +179,83 @@ project.ext { cacheProperties.store(projectPropertiesFile.newWriter(), null) } - // Whether debug symbols should be included. - debugEnabled = findProperty("NDEBUG") == null + // UnityAssetUploader required environment variables. + unityUsername = findProperty("UNITY_USERNAME") + unityPassword = findProperty("UNITY_PASSWORD") + unityPackageId = findProperty("UNITY_PACKAGE_ID") + unityPackagePath = findFileProperty("UNITY_PACKAGE_PATH", null) - // Get the directory that contains this script. - scriptDirectory = buildscript.sourceFile.getParentFile() + // Whether debug symbols should be included. + debugEnabled = true + + // Whether interactive mode tests are enabled. + interactiveModeTestsEnabled = + findProperty("INTERACTIVE_MODE_TESTS_ENABLED", "1") == "1" + + // Whether to continue to the next test if one fails. + continueOnFailForTestsEnabled = + findProperty("CONTINUE_ON_FAIL_FOR_TESTS_ENABLED", "1") == "1" + + // List of test sessions + testSessions = [] + + // List of test types that should be included. Controlled by + // "INCLUDE_TEST_TYPES" property. Includes every tests if the property is + // empty. + // DO NOT USE THIS FOR FILTER, Use `actualIncludeTestTypes` instead. + includeTestTypesParam = + TestTypeEnum.toSet( + findProperty("INCLUDE_TEST_TYPES", "").split('\\s+|\\s*,\\s*').toList(), + true) + + // List of test types that should be excluded. Controlled by + // "EXCLUDE_TEST_TYPES" property. Excludes none if the property is + // empty. + // DO NOT USE THIS FOR FILTER, Use `actualIncludeTestTypes` instead. + excludeTestTypesParam = + TestTypeEnum.toSet( + findProperty("EXCLUDE_TEST_TYPES", "").split('\\s+|\\s*,\\s*').toList(), + false) + + // The actual list of test types to run. + actualIncludeTestTypes = includeTestTypesParam.clone() + actualIncludeTestTypes.removeAll(excludeTestTypesParam) + + // List of test modules that should be included. Controlled by + // "INCLUDE_TEST_MODULES" property. Includes every tests if the property is + // empty. + // DO NOT USE THIS FOR FILTER, Use `actualIncludeTestModules` instead. + includeTestModulesParam = + TestModuleEnum.toSet( + findProperty("INCLUDE_TEST_MODULES", "").split('\\s+|\\s*,\\s*').toList(), + true) + + // List of test modules that should be excluded. Controlled by + // "EXCLUDE_TEST_MODULES" property. Excludes none if the property is + // empty. + // DO NOT USE THIS FOR FILTER, Use `actualIncludeTestModules` instead. + excludeTestModulesParam = + TestModuleEnum.toSet( + findProperty("EXCLUDE_TEST_MODULES", "").split('\\s+|\\s*,\\s*').toList(), + false) + + // The actual list of test module to run. + actualIncludeTestModules = includeTestModulesParam.clone() + actualIncludeTestModules.removeAll(excludeTestModulesParam) + + // List of tests to exclude. Controlled by "EXCLUDE_TESTS" property. Excludes + // none if the property is empty. + excludeTestsParam = + new HashSet(findProperty("EXCLUDE_TESTS", "").toLowerCase().split('\\s+|\\s*,\\s*').toList()) // Directory for intermediate and final build outputs. buildDir = new File(scriptDirectory, "build") + // Directory for external tools. + externalToolsDir = new File(scriptDirectory, "external_tools") // Directory for testing. testDir = new File(scriptDirectory, "test_output") + // Version of the plugin (update this with CHANGELOG.md on each release). + pluginVersion = "1.2.186" // Directory that contains the template plugin. // Files under this directory are copied into the staging area for the // plugin. @@ -175,38 +264,74 @@ project.ext { pluginStagingAreaDir = new File(buildDir, "staging") // Directory where the build plugin is unpacked to. pluginExplodedDir = new File(scriptDirectory, "exploded") + // Directory where the UPM package is unpacked to. + pluginUpmDir = new File(scriptDirectory, "upm") + // Base filename of the released plugin. + currentPluginBasename = "external-dependency-manager" + // Base UPM package name of the released plugin. + currentPluginUpmPackageName = "com.google.external-dependency-manager" // Where the exported plugin file is built before it's copied to the release // location. - pluginExportFile = new File(buildDir, "plugin.unitypackage") + pluginExportFile = new File(buildDir, currentPluginBasename + ".unitypackage") + // Where the exported UPM plugin file is built. + pluginUpmExportFile = new File(buildDir, + currentPluginUpmPackageName + "-" + pluginVersion + ".tgz") // Directory within the plugin staging area that just contains the plugin. - pluginAssetsDir = new File("Assets", "PlayServicesResolver") + pluginAssetsDir = new File("Assets", "ExternalDependencyManager") + // Directories within the staging area to export. + pluginExportDirs = [pluginAssetsDir, new File("Assets", "PlayServicesResolver")] // Directory within the plugin directory that contains the managed DLLs. pluginEditorDllDir = new File(pluginAssetsDir, "Editor") // Directory which contains the solution for all C# projects with a project in // each subdirectory. - pluginSourceDir = new File("source") + pluginSourceDir = new File(scriptDirectory, "source") // Solution which references all projects used by the plugin. - pluginSolutionFile = new File(pluginSourceDir, "JarResolver.sln") - // Version of the plugin (update this with CHANGELOG.md on each release). - pluginVersion = "1.2.111.0" - // Semantic version of the plugin. - pluginVersionSemVer = pluginVersion.tokenize(".")[0..2].join(".") - // Base filename of the released plugin. - currentPluginBasename = "play-services-resolver" + pluginSolutionFile = new File(pluginSourceDir, "ExternalDependencyManager.sln") // Versioned release plugin file. pluginReleaseFile = new File(scriptDirectory, sprintf("%s-%s.unitypackage", currentPluginBasename, pluginVersion)) + // Unversioned release plugin file. + pluginReleaseFileUnversioned = new File(scriptDirectory, + sprintf("%s-latest.unitypackage", + currentPluginBasename)) + + // Location of the Unity asset uploader application. + unityAssetUploaderDir = new File(pluginSourceDir, "UnityAssetUploader") + + // Location of the export_unity_package application. + exportUnityPackageDir = new File(pluginSourceDir, "ExportUnityPackage") + // Location of the import_unity_package application. + importUnityPackageDir = new File(pluginSourceDir, "ImportUnityPackage") // Common arguments used to execute Unity in batch mode. unityBatchModeArguments = ["-batchmode", "-nographics"] + // Common arguments used to execute Unity in interactive mode. + unityInteractiveModeArguments = ["-gvh_noninteractive"] // Extension for Unity asset metadata files. unityMetadataExtension = ".meta" - // Extension for debug files. - dllMdbExtension = ".mdb" + // Extensions for debug files. + symbolDatabaseExtension = pdbSupported ? ".pdb" : ".dll.mdb" // Changelog file. changelog = new File(scriptDirectory, "CHANGELOG.md") + pythonBootstrapDir = new File(externalToolsDir, "python_bootstrap") + pythonBinDir = new File(new File(pythonBootstrapDir, "python"), "bin") + // Python binary after it has been bootstrapped. + pythonExe = new File(pythonBinDir, "python3") + // Pip binary after it has been bootstrapped. + pipExe = new File(pythonBinDir, "pip3") + // Python packages required by export_unity_package.py + exportUnityPackageRequirements = ["absl-py", "PyYAML", "packaging"] + // Python packages required by gen_guids.py + genGuidRequirements = ["absl-py"] +} + +// Configure com.jetbrains.python.envs to bootstrap a Python install. +envs { + bootstrapDirectory = project.ext.pythonBootstrapDir + envsDirectory = new File(project.ext.buildDir, "python_envs") + python "python", "3.9.5" } /* @@ -233,6 +358,254 @@ public enum OperatingSystem { } } +/* + * Test Types + */ +public enum TestTypeEnum { + INTEGRATION, // Unity Integration Tests using IntegrationTester framework. + NUNIT, // Tests using NUnit framework + PYTHON, // Tests implemented in Python + GRADLE // Tests implemented in Gradle scripts + + // A complete set of all enums + private static HashSet completeSet; + + /* + * Get a complete set of all enums + * + * @returns A complete set of all enums + */ + private static HashSet getCompleteSet() { + if (completeSet == null) { + completeSet = new HashSet() + for (TestTypeEnum type : TestTypeEnum.values()) { + completeSet.add(type); + } + } + return completeSet.clone() + } + + /* + * Convert a list of strings to a set of enums + * + * @param values A list of case-insensitive strings to convert to enum. + * @param completeSetWhenEmpty Whether to return a complete set if the list + * is empty. + * + * @returns A set of enums + */ + public static HashSet toSet( + Collection values, Boolean completeSetWhenEmpty) { + def result = new HashSet() + if ( values == null) { + return completeSetWhenEmpty ? getCompleteSet() : result; + } + for (String value : values) { + def trimmed = value.trim().toUpperCase() + if (!trimmed.isEmpty()) { + result.add(TestTypeEnum.valueOf(trimmed)) + } + } + if (result.size() == 0) { + result = completeSetWhenEmpty ? getCompleteSet() : result; + } + return result + } +} + +/* + * Test Modules + */ +public enum TestModuleEnum { + ANDROIDRESOLVER, // Tests for Android Resolver + VERSIONHANDLER, // Tests for Version Handler + IOSRESOLVER, // Tests for iOS Resolver + PACKAGEMANAGER, // Tests for Package Manager + CORE, // Tests for reusable C# libraries + TOOL // Tests for build/packaging/release tools + + // A complete set of all enums + private static HashSet completeSet; + + /* + * Get a complete set of all enums + * + * @returns A complete set of all enums + */ + private static HashSet getCompleteSet() { + if (completeSet == null) { + completeSet = new HashSet() + for (TestModuleEnum type : TestModuleEnum.values()) { + completeSet.add(type); + } + } + return completeSet.clone() + } + + /* + * Convert a list of strings to a set of enums + * + * @param values A list of case-insensitive strings to convert to enum. + * @param completeSetWhenEmpty Whether to return a complete set if the list + * is empty. + * + * @returns A set of enums + */ + public static HashSet toSet( + Collection values, Boolean completeSetWhenEmpty) { + def result = new HashSet() + if ( values == null) { + return completeSetWhenEmpty ? getCompleteSet() : result; + } + for (String value : values) { + def trimmed = value.trim().toUpperCase() + if (!trimmed.isEmpty()) { + result.add(TestModuleEnum.valueOf(trimmed)) + } + } + if (result.size() == 0) { + result = completeSetWhenEmpty ? getCompleteSet() : result; + } + return result + } +} + +/* + * Determine whether the test should be run given the filter parameters, + * the current test type and the current test module + * + * @param type Type of the test. + * @param module Module of the test. + * + * @returns True if the test should be run by the given the filters. + */ +boolean shouldTestRunWithFilters(TestTypeEnum type, TestModuleEnum module) { + project.ext.actualIncludeTestTypes.contains(type) && + project.ext.actualIncludeTestModules.contains(module) +} + +/* + * Set the test type and module to the given task. + * + * @param task The task to set the properties to. + * @param type Type of the test. + * @param module Module of the test. + */ +void setTestProperties(Task task, TestTypeEnum type, TestModuleEnum module) { + task.ext.testType = type + task.ext.testModule = module +} + +/* + * Unit test for TestTypeEnum + */ +task(testTestTypeEnum) { task -> + setTestProperties(task, TestTypeEnum.GRADLE, TestModuleEnum.TOOL) + doFirst { + ReportTestStarted(task) + } + doLast { + def expectedTestTypeEnumCompleteSet = + new HashSet([ + TestTypeEnum.INTEGRATION, + TestTypeEnum.NUNIT, + TestTypeEnum.PYTHON, + TestTypeEnum.GRADLE]) + def expectedEmptySet = new HashSet() + def expectedPythonOnlySet = new HashSet([TestTypeEnum.PYTHON]) + def expectedPythonAndIntegrationSet = new HashSet([ + TestTypeEnum.PYTHON, + TestTypeEnum.INTEGRATION]) + + assert TestTypeEnum.getCompleteSet().equals( + expectedTestTypeEnumCompleteSet) + assert TestTypeEnum.toSet([], false).equals( + expectedEmptySet) + assert TestTypeEnum.toSet([], true).equals( + expectedTestTypeEnumCompleteSet) + assert TestTypeEnum.toSet(["python"], false).equals( + expectedPythonOnlySet) + assert TestTypeEnum.toSet(["python"], true).equals( + expectedPythonOnlySet) + assert TestTypeEnum.toSet(["PYTHON"], false).equals( + expectedPythonOnlySet) + assert TestTypeEnum.toSet(["PyThOn"], false).equals( + expectedPythonOnlySet) + assert TestTypeEnum.toSet(["Python", "Integration"], false).equals( + expectedPythonAndIntegrationSet) + assert TestTypeEnum.toSet(["Integration", "Python"], false).equals( + expectedPythonAndIntegrationSet) + assert TestTypeEnum.toSet( + ["Integration", "Python", "Gradle", "NUnit"], false).equals( + expectedTestTypeEnumCompleteSet) + + EvaluateTestResult(task) + } +} + +/* + * Unit test for TestModuleEnum + */ +task(testTestModuleEnum) { task -> + setTestProperties(task, TestTypeEnum.GRADLE, TestModuleEnum.TOOL) + doFirst { + ReportTestStarted(task) + } + doLast { + def expectedTestModuleEnumCompleteSet = + new HashSet([ + TestModuleEnum.ANDROIDRESOLVER, + TestModuleEnum.VERSIONHANDLER, + TestModuleEnum.IOSRESOLVER, + TestModuleEnum.PACKAGEMANAGER, + TestModuleEnum.CORE, + TestModuleEnum.TOOL]) + def expectedEmptySet = new HashSet() + def expectedToolOnlySet = new HashSet([TestModuleEnum.TOOL]) + def expectedToolAndAndroidResolverSet = new HashSet([ + TestModuleEnum.TOOL, + TestModuleEnum.ANDROIDRESOLVER]) + + assert TestModuleEnum.getCompleteSet().equals( + expectedTestModuleEnumCompleteSet) + assert TestModuleEnum.toSet([], false).equals( + expectedEmptySet) + assert TestModuleEnum.toSet([], true).equals( + expectedTestModuleEnumCompleteSet) + assert TestModuleEnum.toSet(["tool"], false).equals( + expectedToolOnlySet) + assert TestModuleEnum.toSet(["tool"], true).equals( + expectedToolOnlySet) + assert TestModuleEnum.toSet(["TOOL"], false).equals( + expectedToolOnlySet) + assert TestModuleEnum.toSet(["TooL"], false).equals( + expectedToolOnlySet) + assert TestModuleEnum.toSet(["Tool", "AndroidResolver"], false).equals( + expectedToolAndAndroidResolverSet) + assert TestModuleEnum.toSet(["AndroidResolver", "Tool"], false).equals( + expectedToolAndAndroidResolverSet) + assert TestModuleEnum.toSet([ + "AndroidResolver", + "VersionHandler", + "iOSResolver", + "PackageManager", + "Core", + "Tool"], false).equals( + expectedTestModuleEnumCompleteSet) + EvaluateTestResult(task) + } +} + +/* + * Test session + */ +public class TestSession { + public String name; + public TestTypeEnum type; + public TestModuleEnum module; + public Boolean isPassed; +} + /* * Search the path variable for an executable file. * @@ -342,24 +715,6 @@ void saveProperty(String name, value, Properties properties) { logger.info(sprintf("%s: %s", name, value)) } -/* - * Make sure the NUnit DLL property is set. - */ -void checkNUnitDllPath() { - if (project.ext.unityNUnitDll == null) { - throw new StopActionException("NUnit DLL not found.") - } -} - -/* - * Make sure the NUnit console property is set. - */ -void checkNUnitConsolePath() { - if (project.ext.nunitConsoleExe == null) { - throw new StopActionException("NUnit Console not found.") - } -} - /* * Removes the extension from a filename. * @@ -370,9 +725,14 @@ void checkNUnitConsolePath() { */ List splitFilenameExtension(File fileObj) { String filename = fileObj.name - int extensionIndex = - (filename - project.ext.unityMetadataExtension - - project.ext.dllMdbExtension).lastIndexOf(".") + String trimmedFilename = filename + if (trimmedFilename.endsWith(project.ext.unityMetadataExtension)) { + trimmedFilename -= project.ext.unityMetadataExtension + } + if (trimmedFilename.endsWith(".dll.mdb")) { + trimmedFilename -= ".mdb" + } + int extensionIndex = trimmedFilename.lastIndexOf(".") if (extensionIndex < 0) return [filename, ""] String basename = filename.substring(0, extensionIndex) String extension = filename.substring(extensionIndex) @@ -381,49 +741,83 @@ List splitFilenameExtension(File fileObj) { /* * Construct the name of a versioned asset from the source filename and version - * string. The encoded string takes the form + * string. If fullVersionPrefix is true, the encoded string takes the form + * ${filename}_version-${version}.${extension} + * if fullVersionPrefix is false, the string takes the form * ${filename}_v${version}.${extension} * where extension is derived from the specified filename. * * @param fileObj File to add version to. + * @param fullVersionPrefix if true uses the "_version-" otherwise uses "_v-". + * @param postfix Optional string to add before the extensioon. + * @param useVersionDir If true, place the file to be under a folder named after + * the version number, instead of changing the filename. * * @returns File which includes an encoded version. */ -File versionedAssetFile(File fileObj) { +File versionedAssetFile(File fileObj, Boolean fullVersionPrefix, + String postfix, Boolean useVersionDir) { String basename String extension (basename, extension) = splitFilenameExtension(fileObj) // Encode the DLL version and target names into the DLL in the form... - // ${dllname}_v${version}.dll + // ${dllname}_version-${version}.dll String targetName = basename String version = project.ext.pluginVersion + File dllDir = fileObj.parent != null ? new File(fileObj.parent) : + new File() if (!(version == null || version.isEmpty())) { - targetName += "_v" + version + if (useVersionDir) { + dllDir = new File(dllDir, version) + } else { + targetName += (fullVersionPrefix ? "_version-" : "_v") + version + } } - String filename = targetName + extension - return fileObj.parent != null ? new File(fileObj.parent, filename) : - new File(filename) + String filename = targetName + postfix + extension + return new File(dllDir, filename) } /* - * Remove the version component from a filename. + * Remove the version component from a path. (Both its filename and its parent + * folder) * * @param fileObj File to remove version from. * * @returns File with removed version string. */ File unversionedAssetFile(File fileObj) { + // Remove the version postfix. Ex. + // "ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.166.dll" -> + // "ExternalDependencyManager/Editor/Google.IOSResolver.dll" String basename String extension (basename, extension) = splitFilenameExtension(fileObj) - def versionRegEx = /^(.*)_(v[^v]+)$/ - def versionMatch = basename =~ versionRegEx + def versionRegExFull = /^(.*)_(version-[^-]+)$/ + def versionRegExShort = /^(.*)_(v[^v]+)$/ + def versionMatch = basename =~ versionRegExShort if (versionMatch.matches()) { basename = versionMatch.group(1) + } else { + versionMatch = basename =~ versionRegExFull + if (versionMatch.matches()) { + basename = versionMatch.group(1) + } } String filename = basename + extension - return fileObj.parent != null ? - new File(fileObj.parent, filename) : new File(filename) + + // Remove the version folder as well. Ex. + // "ExternalDependencyManager/Editor/1.2.166/Google.IOSResolver.dll" -> + // "ExternalDependencyManager/Editor/Google.IOSResolver.dll" + def versionFolderRegEx = /^[0-9]+\.[0-9]+\.[0-9]+$/ + File parent = fileObj.parent != null ? new File(fileObj.parent) : null + if (parent != null) { + String parentFolder = parent.name + def folderMatch = parentFolder =~ versionFolderRegEx + if (folderMatch.matches()) { + parent = parent.parent != null ? new File(parent.parent) : null + } + } + return parent != null ? new File(parent, filename) : new File(filename) } /* @@ -500,11 +894,12 @@ File copyAssetMetadataFile(File sourceFile, File targetFile) { if (!sourceFile.name.endsWith(project.ext.unityMetadataExtension)) { return copyFile(sourceFile, targetFile) } - String[] lines = sourceFile.text.split("\n") + String[] lines = sourceFile.text.tokenize("\n") // Parse the existing version from the asset metadata. def folderAssetRegEx = /^folderAsset:\s+yes\s*$/ def versionRegEx = /^(-\s+gvh_version-)([a-zA-Z0-9.]+)\s*$/ + def exportPathRegEx = /^(-\s+gvhp_exportpath-)(.*)$/ Boolean isFolder = false String currentVersion = "" lines.each { String line -> @@ -516,8 +911,11 @@ File copyAssetMetadataFile(File sourceFile, File targetFile) { isFolder = true } } + Boolean isNotVersioned = (isFolder || + targetFile.name.startsWith( + "play-services-resolver")) // Ignore folder assets, they don't need to be versioned. - if (isFolder) return copyFile(sourceFile, targetFile) + if (isNotVersioned) return copyFile(sourceFile, targetFile) Boolean versionChanged = currentVersion != project.ext.pluginVersion List outputLines = [] @@ -525,7 +923,9 @@ File copyAssetMetadataFile(File sourceFile, File targetFile) { if (versionChanged) { def guidMatch = (line =~ /^(guid:)\s+(.*)/) def versionLabelMatch = (line =~ versionRegEx) - if (guidMatch.matches()) { + def exportPathMatch = (line =~ exportPathRegEx) + if (guidMatch.matches() && (sourceFile.name != targetFile.name || + sourceFile.parent != targetFile.parent ) ) { // Update the metadata's GUID. // If a file is renamed we want to make sure Unity imports it as a new // asset with the new filename. @@ -533,11 +933,16 @@ File copyAssetMetadataFile(File sourceFile, File targetFile) { "%s %s", guidMatch.group(1), java.util.UUID.randomUUID().toString().replace("-", "")) - } - else if (versionLabelMatch.matches()) { + } else if (versionLabelMatch.matches()) { // Update the version metadata for the asset. line = sprintf("%s%s", versionLabelMatch.group(1), project.ext.pluginVersion) + } else if (exportPathMatch.matches()) { + // Update the export path of the asset. + line = sprintf("%s%s", exportPathMatch.group(1), + targetFile.path.replaceFirst(/.*\/Assets\//, "")) + line = line.substring(0, line.length() - + project.ext.unityMetadataExtension.length()) } } outputLines.add(line) @@ -575,7 +980,18 @@ Task createXbuildTask(String taskName, String taskDescription, Iterable outputFilesInBinaryOutputDir = outputFiles.collect { return new File(binaryOutputDir, it.path) } + + if (project.ext.debugEnabled) { + outputFilesInBinaryOutputDir += outputFilesInBinaryOutputDir.findResults { + return it.name.endsWith(".dll") ? + new File(it.parentFile.path, + splitFilenameExtension(it)[0] + + project.ext.symbolDatabaseExtension) : null + } + } + Iterable dependsOnTasks = dependsOn ? dependsOn : [] + Iterable compileTaskDependencies = dependsOnTasks.clone() Iterable patchVersionFilesTasks = inputFiles.findResults { if (it.name == "VersionNumber.cs") { File versionFile = it @@ -604,31 +1020,35 @@ Task createXbuildTask(String taskName, String taskDescription, return patchVersionTask } } + if (patchVersionFilesTasks) { + compileTaskDependencies += patchVersionFilesTasks + } + Task task = tasks.create(name: taskName, description: taskDescription, - type: Exec, - dependsOn: patchVersionFilesTasks ? - patchVersionFilesTasks : - dependsOnTasks).with { + type: Task, + dependsOn: compileTaskDependencies).with { inputs.files inputFiles outputs.files files(outputFilesInBinaryOutputDir) - executable project.ext.xbuildExe - workingDir projectToBuild.parentFile.absolutePath - args ([sprintf("/target:%s", target), - sprintf("/property:UnityHintPath=%s", - project.ext.unityDllPath.absolutePath), - sprintf("/property:UnityIosPath=%s", - project.ext.unityIosPath.absolutePath), - sprintf("/property:NUnityHintPath=%s", - project.ext.unityNUnitDll ? - project.ext.unityNUnitDll.absolutePath: ""), - sprintf("/property:BaseIntermediateOutputPath=%s%s", - intermediatesDir.absolutePath, - File.separator), - sprintf("/property:OutputPath=%s%s", - binaryOutputDir.absolutePath, - File.separator), - projectToBuild.absolutePath]) + doLast { + exec { + executable project.ext.xbuildExe + workingDir projectToBuild.parentFile.absolutePath + args ([sprintf("/target:%s", target), + sprintf("/property:UnityHintPath=%s", + project.ext.unityDllPath.absolutePath), + sprintf("/property:UnityIosPath=%s", + project.ext.unityIosPath.absolutePath), + sprintf("/property:BaseIntermediateOutputPath=%s%s", + intermediatesDir.absolutePath, + File.separator), + sprintf("/property:OutputPath=%s%s", + binaryOutputDir.absolutePath, + File.separator), + "/verbosity:quiet", + projectToBuild.absolutePath]) + } + } } task.ext.buildDir = outputDir return task @@ -658,25 +1078,21 @@ Task createBuildPluginDllTask(String componentName, File projectDir = new File(project.ext.pluginSourceDir, projectName) File projectBuildDir = new File(project.ext.buildDir, projectName) - List buildFiles = [new File(assemblyDllBasename)] - if (project.ext.debugEnabled) { - buildFiles += [new File(assemblyDllBasename + project.ext.dllMdbExtension)] - } - // Compile the C# project. Task compileTask = createXbuildTask( sprintf("compile%s", componentName), sprintf("Compile %s", projectName), project.ext.pluginSolutionFile, projectName, fileTree(new File(projectDir, "src")), projectBuildDir, - buildFiles, dependsOn) + [new File(assemblyDllBasename)], dependsOn) compileTask.ext.componentName = componentName // Template metadata for the built DLL. - Iterable assemblyDllMetaFiles = buildFiles.collect { - new File(new File(project.ext.pluginTemplateDir, - project.ext.pluginEditorDllDir.path), - it.name + project.ext.unityMetadataExtension) - } + Iterable assemblyDllMetaFiles = + compileTask.outputs.files.collect { + new File(new File(project.ext.pluginTemplateDir, + project.ext.pluginEditorDllDir.path), + it.name + project.ext.unityMetadataExtension) + } // Optionally map unversioned to versioned filenames. Iterable unversionedFiles = files(compileTask.outputs.files, assemblyDllMetaFiles) @@ -691,7 +1107,7 @@ Task createBuildPluginDllTask(String componentName, return [ unversionedFile.path, versionDll ? - versionedAssetFile(unversionedOutputFile) : + versionedAssetFile(unversionedOutputFile, false, "", true) : unversionedOutputFile] } @@ -715,7 +1131,7 @@ Task createBuildPluginDllTask(String componentName, copyTask.ext.componentName = componentName // Generate a clean target for this project. - Task cleanTask = tasks.create(name: sprintf("clean%s", projectName), + Task cleanTask = tasks.create(name: sprintf("clean%s", componentName), description: sprintf("Clean %s plugin DLL", projectName), type: Delete).with { delete (files(compileTask.outputs.files, @@ -735,33 +1151,92 @@ Task createBuildPluginDllTask(String componentName, } /* - * Create a Nunit test task. + * Creates a task which compiles and run an NUnit test. + * + * @param taskName Name of the test. + * @param description Description of the task. + * @param dependsOn Dependencies of the new task. + * @param unityProjectDir Directory containing a assets to copy into the + * integration test project. + * @param arguments Additional arguments for Unity when running the integration + * test. + * @param testType Type of the test + * @param testModule Module of the test + */ +Task createUnityNUnitTest(String taskName, + String description, + Iterable dependsOn, + File unityProjectDir, + Iterable arguments, + TestTypeEnum testType, + TestModuleEnum testModule) { + createUnityTestBatchAndNonBatch( + taskName, + description, + ["buildPlugin"], + unityProjectDir, + [], + arguments + [ + "-runTests", + "-batchmode", + "-testResults", "results.xml", + "-testPlatform", "EditMode" + ], null, testType, testModule, + true, + new File(new File(new File( + project.ext.scriptDirectory, + "test_resources"), + "nunit_upm"), + "manifest.json")) +} + +/* + * Create a task to run an executable. * * @param name Name of the task. * @param description Description of the task. - * @param testDll Test DLL to execute. The log file will be placed in the - * parent directory of this DLL path. + * @param dependsOn Tasks this depends upon. + * @param executable Executable to run. + * @param arguments Arguments for the executable. + * @param continueOnFail Whether to ignore non-zero return code and continue. + * + * @returns Task which runs the specified executable. + */ +Task createExecTask(String name, String description, + Iterable dependsOn, File executableToRun, + Iterable arguments, Boolean continueOnFail = false) { + Task execTask = tasks.create(name: name, + description: description, + type: Exec, + dependsOn: dependsOn) + execTask.with { + executable executableToRun + args arguments + ignoreExitValue continueOnFail + } + return execTask +} + +/* + * Create a task that does nothing. + * + * @param taskName Name of the task to create. + * @param summary Description of the task. * @param dependsOn Dependencies of the new task. * - * @returns Task which executes nunit-console. + * @returns Task that does nothing. */ -Task createNUnitTask(String name, String description, File testDll, +Task createEmptyTask(String taskName, String summary, Iterable dependsOn) { - File logFileDir = testDll.parentFile.parentFile - File logFile = new File(logFileDir, name + ".log") - File xmlLogFile = new File(logFileDir, name + ".xml") - return tasks.create(name: name, - description: description, - type: Exec, - dependsOn: dependsOn).with { - workingDir project.ext.pluginSourceDir - outputs.files logFile - executable project.ext.nunitConsoleExe - args ([sprintf("-output:%s", logFile.absolutePath), - sprintf("-xml:%s", xmlLogFile.absolutePath), - testDll.absolutePath]) - doFirst { checkNUnitConsolePath() } + Task emptyTask = tasks.create(name: taskName, + description: sprintf("(disabled) %s", summary), + dependsOn: dependsOn) + emptyTask.with { + doLast { + logger.info(sprintf("%s disabled", taskName)) + } } + return emptyTask } /* @@ -777,6 +1252,9 @@ Task createNUnitTask(String name, String description, File testDll, * @param projectContainerDir Directory that contains the project directory. * @param arguments Command line arguments to pass to Unity. * @param batchMode Whether to run Unity in batch mode. + * @param createTaskClosure Optional task used to start Unity, this must + * conform to createExecTask() + * @param continueOnFail Whether to ignore non-zero return code and continue. * * @returns Task which executes Unity. * The following extended properties are set on the task: @@ -788,29 +1266,53 @@ Task createNUnitTask(String name, String description, File testDll, Task createUnityTask(String taskName, String summary, Iterable dependsOn, String projectName, File projectContainerDir, Iterable arguments, - Boolean batchMode) { + Boolean batchMode, createTaskClosure, + Boolean continueOnFail = false) { Boolean createProject = summary == "create" File logFile = new File(projectContainerDir, sprintf("%s_%s.log", projectName, summary)) File projectDir = new File(projectContainerDir, projectName) List executeArguments = [] - if (batchMode) executeArguments += unityBatchModeArguments + + if (batchMode) { + executeArguments += project.ext.unityBatchModeArguments + } else { + executeArguments += project.ext.unityInteractiveModeArguments + } executeArguments += [ "-logFile", logFile.absolutePath, createProject ? "-createProject" : "-projectPath", projectDir.absolutePath] if (createProject) executeArguments += ["-quit"] executeArguments += arguments - Task unityTask = tasks.create(name: taskName, - description: sprintf( - "Run Unity to %s (project: %s)", - summary, projectName), - type: Exec, - dependsOn: dependsOn).with { - inputs.files fileTree(projectDir) + if (!createTaskClosure) { + createTaskClosure = { + String name, String description, Iterable depends, + File executable, Iterable args, Boolean contOnFail-> + return createExecTask(name, + description, + depends, + executable, + args, + contOnFail) + } + } + + Task unityTask + if (!batchMode && !project.ext.interactiveModeTestsEnabled) { + unityTask = createEmptyTask(taskName, summary, dependsOn) + } else { + unityTask = createTaskClosure(taskName, + sprintf( + "Run Unity to %s (project: %s)", + summary, projectName), + dependsOn, + project.ext.unityExe, + executeArguments, + continueOnFail) + } + unityTask.with { outputs.files files(logFile) - executable project.ext.unityExe - args executeArguments } unityTask.ext.projectDir = projectDir unityTask.ext.containerDir = projectContainerDir @@ -836,7 +1338,7 @@ Task createUnityTask(String taskName, String summary, Task createUnityProjectTask(String taskName, Iterable dependsOn, String projectName, File projectContainerDir) { return createUnityTask(taskName, "create", dependsOn, projectName, - projectContainerDir, [], true).with { + projectContainerDir, [], true, null).with { doFirst { // Clean / create the output directory. delete ext.containerDir @@ -845,35 +1347,6 @@ Task createUnityProjectTask(String taskName, Iterable dependsOn, } } -/* - * Setup a Unity project for a testing task and import the plugin in - * preparation for testing. - * - * @param taskName Name of the test task. - * @param dependsOn Dependencies of the new task. - * @param projectName Name of the Unity project to create. - * @param batchMode Whether to run Unity in batch mode. - * - * @returns Task which sets up a Unity project. - * The following extended properties are set on the task: - * - ext.projectDir: Directory of the created Unity project. - * - ext.containerDir: Directory which contains the Unity project and the logs. - * - ext.logFile: Unity log file for the import operation. - */ -Task createSetupUnityProjectTask(String taskName, Iterable dependsOn, - String projectName, Boolean batchMode) { - File outputDir = new File(project.ext.testDir, projectName) - Task createProject = createUnityProjectTask( - sprintf("create%s", taskName), dependsOn, projectName, outputDir) - createProject.with { - inputs.files files(project.ext.pluginExportFile) - } - return createUnityTask( - taskName, "import_plugin", [createProject], projectName, outputDir, - ["-importPackage", project.ext.pluginExportFile.absolutePath, - "-quit"], batchMode) -} - /* * Creates a task which runs a test with the Unity plugin. * @@ -885,36 +1358,147 @@ Task createSetupUnityProjectTask(String taskName, Iterable dependsOn, * @param additionalArguments Additional arguments to pass to Unity when * executing the test. * @param batchMode Whether to execute Unity in batch mode. + * @param editorAssets Files to copy into the Assets/Editor folder in the + * project. + * @param createTaskClosure Optional task used to start Unity, this must + * conform to tasks.create(name, description, type, dependsOn). + * @param testType Type of the test + * @param testModule Module of the test + * @param enableDlls Whether to enable dlls through Version Handlers before tests. + * @param upm_package_manifest Specify UPM package manifest (manifest.json) * * @returns Test task. */ Task createUnityTestTask(String taskName, String description, Iterable dependsOn, File testScriptsDir, + Iterable editorAssets, Iterable additionalArguments, - Boolean batchMode) { - List inputFiles = fileTree(testScriptsDir).collect { - new File(it.absolutePath - testScriptsDir.absolutePath) + Boolean batchMode, createTaskClosure, + TestTypeEnum testType, TestModuleEnum testModule, + Boolean enableDlls = false, + File upm_package_manifest = null) { + List testScripts = [] + if (testScriptsDir) { + testScripts = fileTree(testScriptsDir).collect { + new File(it.absolutePath.substring( + testScriptsDir.absolutePath.length() + 1)) + } } + // Setup the Unity project. - Task setupTestProject = createSetupUnityProjectTask( - sprintf("setup%s", taskName), dependsOn, taskName, batchMode) - setupTestProject.with { - inputs.files inputFiles + List setupTasks = [] + File testOutputDir = new File(project.ext.testDir, taskName) + Task createProject = createUnityProjectTask( + sprintf("createsetup%s", taskName), dependsOn, taskName, testOutputDir) + createProject.with { task -> + inputs.files files(project.ext.pluginExportFile) + setTestProperties(task, testType, testModule) + } + setupTasks += [createProject] + + Task setupTestProject = createUnityTask( + sprintf("setup%s", taskName), "import_plugin", [createProject], taskName, + testOutputDir, ["-importPackage", project.ext.pluginExportFile.absolutePath, + "-quit"], batchMode, null) + setupTestProject.with { task -> + inputs.files (testScripts + editorAssets) + setTestProperties(task, testType, testModule) + } + setupTasks += [setupTestProject] + + Task versionHandlerUpdate; + if (enableDlls) { + File updaterScriptDir = new File(new File(new File( + setupTestProject.ext.projectDir, + "Assets"), + "Editor"), + "VersionHandlerUpdater") + versionHandlerUpdate = createUnityTask( + sprintf("enableDlls%s", taskName), "enable_dlls", [setupTestProject], taskName, + testOutputDir, [], batchMode, null) + versionHandlerUpdate.with { task -> + setTestProperties(task, testType, testModule) + doFirst { + copy { + from new File(new File(new File( + project.ext.scriptDirectory, + "test_resources"), + "version_handler_update"), + "VersionHandlerUpdater.cs") + into updaterScriptDir + } + } + doLast { + delete updaterScriptDir + } + } + setupTasks += [versionHandlerUpdate] } + List copyTasks = [] + // Task which copies test scripts into the project. - Task copyTestScripts = createCopyFilesTask( - sprintf("copyScriptsFor%s", taskName), - sprintf("Copy the test scripts into the %s Unity project.", taskName), - inputFiles, testScriptsDir, setupTestProject.ext.projectDir, - [setupTestProject], null) + if (testScriptsDir) { + Task copyTestScripts = createCopyFilesTask( + sprintf("copyScriptsFor%s", taskName), + sprintf("Copy the test scripts into the %s Unity project.", taskName), + testScripts, testScriptsDir, setupTestProject.ext.projectDir, + setupTasks, null) + copyTestScripts.with { task -> + setTestProperties(task, testType, testModule) + } + copyTasks += [copyTestScripts] + } + + if (upm_package_manifest != null) { + Task copyPackageManifest = tasks.create( + name: sprintf("copyPackageManifestFor%s", taskName), + description: sprintf("Copy the package manifest into the %s Unity project.", + taskName), + type: Copy, + dependsOn: setupTasks) + copyPackageManifest.with { task -> + from upm_package_manifest + into new File(setupTestProject.ext.projectDir, "Packages") + setTestProperties(task, testType, testModule) + } + copyTasks += [copyPackageManifest] + } + + // Task which copies editor scripts into the project. + Task copyEditorAssets = tasks.create( + name: sprintf("copyEditorAssetsFor%s", taskName), + description: sprintf("Copy the editor assets into the %s Unity project.", + taskName), + type: Copy, + dependsOn: setupTasks) + copyEditorAssets.with { task -> + from editorAssets + into new File(new File(setupTestProject.ext.projectDir, "Assets"), + "Editor") + setTestProperties(task, testType, testModule) + } + copyTasks += [copyEditorAssets] // Create the test task. - Task testTask = createUnityTask(taskName, "test", [copyTestScripts], + Task testTask = createUnityTask(taskName, "test", + copyTasks, setupTestProject.ext.projectDir.name, setupTestProject.ext.containerDir, - additionalArguments, batchMode) + additionalArguments, batchMode, + createTaskClosure, + true) testTask.description = description + testTask.with { task -> + finalizedBy "reportAllTestsResult" + doLast { + EvaluateTestResult(task) + } + doFirst { + ReportTestStarted(task) + } + setTestProperties(task, testType, testModule) + } // Create a clean task Task cleanTestTask = tasks.create(name: sprintf("clean%s", taskName), @@ -935,84 +1519,373 @@ Task createUnityTestTask(String taskName, String description, * @param dependsOn Dependencies of the new task. * @param testScriptsDir Directory containing scripts to copy into the test * project and execute for testing. - * @param batchMode Whether to execute Unity in batch mode. + * @param editorAssets Files to copy into the Assets/Editor folder in the + * project. + * @param additionalArguments Additional arguments to pass to Unity. + * @param createTaskClosure Optional task used to start Unity, this must + * conform to tasks.create(name, description, type, dependsOn). + * @param testType Type of the test + * @param testModule Module of the test + * @param enableDlls Whether to enable dlls through Version Handlers before tests. + * @param upm_package_manifest Specify UPM package manifest (manifest.json) * * @returns Test task. */ Task createUnityTestBatchAndNonBatch(String taskName, String description, Iterable dependsOn, File testScriptsDir, - Iterable additionalArguments) { + Iterable editorAssets, + Iterable additionalArguments, + createTaskClosure, + TestTypeEnum testType, + TestModuleEnum testModule, + Boolean enableDlls = false, + File upm_package_manifest = null) { return tasks.create(name: taskName, description: description, dependsOn: [ createUnityTestTask( sprintf("%sBatchMode", taskName), sprintf("%s (Batch Mode)", description), - dependsOn, testScriptsDir, additionalArguments, true), + dependsOn, testScriptsDir, editorAssets, + additionalArguments, true, createTaskClosure, + testType, testModule, + enableDlls, upm_package_manifest), createUnityTestTask( sprintf("%sInteractiveMode", taskName), sprintf("%s (Interactive Mode)", description), - dependsOn, testScriptsDir, additionalArguments, - true)]) + dependsOn, testScriptsDir, editorAssets, + additionalArguments, false, createTaskClosure, + testType, testModule, + enableDlls, upm_package_manifest)]) } -Task compileResolverLibTests = createXbuildTask( - "compileResolverLibTests", - "Compile tests for the deprecated Jar Resolver library.", - project.ext.pluginSolutionFile, "JarResolverTests", - fileTree(new File(new File(project.ext.pluginSourceDir, - "JarResolverLib"), "src")), - new File(project.ext.testDir, "ResolverLibTests"), - [new File("JarResolverTests.dll")], []).with { - doFirst { checkNUnitDllPath() } +/* + * Install Python packages. + * + * @param taskName Name of the task to create. + * @param description Description of the task. + * @param dependsOn Dependencies of the new task. + * @param packages Packages to install. + * + * @returns Task which executes pip to install packages + */ +Task createInstallPythonPackageTask(String taskName, String description, + Iterable dependsOn, + Iterable packages) { + Task installPythonPackageTask = tasks.create( + name: taskName, + description: sprintf("Run Pip to %s", description), + type: Exec, + dependsOn: dependsOn + ["build_envs"]).with { + executable project.ext.pipExe + args (["-q", "install"] + packages) + } } -Task testResolverLibTests = createNUnitTask( - "testResolverLibTests", - "Runs the tests for the deprecated Jar Resolver library", - compileResolverLibTests.outputs.files[0], - [compileResolverLibTests]) +/* + * Create a task to execute Python. + * + * @param taskName Name of the task to create. + * @param description Description of the task. + * @param dependsOn Dependencies of the new task. + * @param script Python script to run. + * @param arguments Command line arguments to pass to the Python script. + * @param packages Optional Python packages to install. + * @param continueOnFail Whether to ignore non-zero return code and continue. + * + * @returns Task which executes Python. + */ +Task createPythonTask(String taskName, String description, + Iterable dependsOn, + File script, Iterable arguments, + Iterable packages, + Boolean continueOnFail = false) { + List installPackagesTask = [] + if (packages) { + installPackagesTask = [ + createInstallPythonPackageTask( + taskName + "InstallPipPackages", + sprintf("install packages %s for %s", packages.toString(), taskName), + [], + packages) + ] + } + Task pythonTask = tasks.create( + name: taskName, + description: sprintf("Run Python to %s", description), + type: Exec, + dependsOn: (dependsOn + installPackagesTask + ["build_envs"])).with { + ignoreExitValue continueOnFail + executable project.ext.pythonExe + args ([script.absolutePath] + arguments) + } + return pythonTask +} -task cleanResolverLibTests() { - description "Clean test output for the deprecated Jar Resolver library" - doLast { delete files(compileResolverLibTests.ext.buildDir, - testResolverLibTests.outputs.files) } +/* + * Creates a task that packages a Unity plugin with export_unity_package.py. + * + * @param taskName Name of the task to create. + * @param description Description of the task. + * @param dependsOn Dependencies of the new task. + * @param configFile Configuration file which specifies input files. + * @param guidsFile Optional GUIDs database file. + * @param assetsDir Input directory for assets referenced by the configFile. + * @param generateUnitypackage Whether to create a .unitypackage. + * @param generateUpmTarball Whether to create a UPM tarball. + * @param pluginsVersion Version to apply to exported plugins. + * @param outputDir Directory to write the the exported archives. + * @param arguments Additional arguments for export_unity_package.py + */ +Task createExportUnityPackageTask(String taskName, + String description, + Iterable dependsOn, + File configFile, + File guidsFile, + File assetsDir, + Boolean generateUnityPackage, + Boolean generateUpmTarball, + String pluginVersion, + File outputDir, + Iterable arguments) { + File exportScript = new File(project.ext.exportUnityPackageDir, + "export_unity_package.py") + Task exportUnityPackageTask = createPythonTask( + taskName, + description, + dependsOn, + exportScript, + ["--config_file", configFile, + "--assets_dir", assetsDir, + "--plugins_version", pluginVersion, + "--output_dir", outputDir] + + [generateUnityPackage ? + "--output_unitypackage" : "--nooutput_unitypackage"] + + [generateUpmTarball ? "--output_upm" : "--nooutput_upm"] + + (guidsFile ? ["--guids_file", guidsFile] : []) + + ["-v", "-1"] + // Only display warnings. + arguments, + exportUnityPackageRequirements) + exportUnityPackageTask.with { + inputs.files ([configFile] + + (guidsFile ? [guidsFile] : []) + + fileTree(assetsDir) + + [exportScript]) + } + return exportUnityPackageTask } -Task compilePackageManagerTests = createXbuildTask( - "compilePackageManagerTests", - "Compile tests for the package manager.", - project.ext.pluginSolutionFile, "PackageManagerTests", - fileTree(new File(new File(project.ext.pluginSourceDir, - "PackageManagerTests"), "src")), - new File(project.ext.testDir, "PackageManagerTests"), - [new File("PackageManagerTests.dll")], []).with { - doFirst { checkNUnitDllPath() } +Task createGenGuidTask(String taskName, + String description, + File guidsFile, + String pluginVersion, + Iterable guidPath) { + File genGuidScript = new File(project.ext.exportUnityPackageDir, + "gen_guids.py") + Task genGuidTask = createPythonTask( + taskName, + description, + [], + genGuidScript, + ["--version", pluginVersion, + "--guids_file", guidsFile] + + guidPath, + genGuidRequirements) + genGuidTask.with { + inputs.files ([guidsFile] + + [genGuidScript]) + } + return genGuidTask } -Task testPackageManagerTests = createNUnitTask( - "testPackageManagerTests", - "Runs tests for the Package Manager", - compilePackageManagerTests.outputs.files[0], - [compilePackageManagerTests]).with { - environment (["TEST_DATA_DIR": - (new File(new File(project.ext.pluginSourceDir, - "PackageManagerTests"), - "testData")).absolutePath]) + +createUnityNUnitTest( + "testAndroidResolverNUnitTests", + "Runs NUnit tests for the Android Resolver module.", + [], + new File(project.ext.scriptDirectory, + "source/AndroidResolver/unit_tests"), [], + TestTypeEnum.NUNIT, TestModuleEnum.ANDROIDRESOLVER +) + +task testDownloadArtifacts(type: GradleBuild) { task -> + description "Run tests for the download_artifacts.gradle script." + dir "source/AndroidResolver/scripts" + setTestProperties(task, TestTypeEnum.GRADLE, TestModuleEnum.ANDROIDRESOLVER) + doLast { + EvaluateTestResult(task) + } + doFirst { + ReportTestStarted(task) + } + finalizedBy "reportAllTestsResult" } -task cleanPackageManagerTests() { - description "Clean Package Manager tests" - doLast { delete files(compilePackageManagerTests.ext.buildDir, - testPackageManagerTests.outputs.files) } + +/* + * Report when a test starts to run + * + * @param testTask Task for test to start. + */ +void ReportTestStarted(Task testTask) { + println sprintf("Test %s STARTED", testTask.name) } -task testDownloadArtifacts(type: GradleBuild) { - description "Run tests for the download_artifacts.gradle script." - buildFile "source/PlayServicesResolver/scripts/download_artifacts_test.gradle" +/* + * Evaluate previously-ran test result + * + * @param testTask Task for previously-ran test + */ +void EvaluateTestResult(Task testTask) { + Boolean succeeded = false + if (testTask.class.simpleName.startsWith("Exec")) { + if (testTask.execResult.exitValue == 0) { + succeeded = true + } + } else if (testTask.class.simpleName.startsWith("DefaultTask") || + testTask.class.simpleName.startsWith("GradleBuild")) { + if (testTask.state.didWork && testTask.state.failure == null) { + succeeded = true + } + } else { + throw new GradleException( + sprintf("Unsupported test class %s", testTask.class.simpleName)) + } + + if (succeeded) { + println sprintf("Test %s PASSED", testTask.name) + project.ext.testSessions.add(new TestSession( + name: testTask.name, + type: testTask.ext.testType, + module: testTask.ext.testModule, + isPassed: true)) + } else { + String errorMsg = sprintf("Test %s FAILED", testTask.name) + println sprintf("::error::%s", errorMsg) + project.ext.testSessions.add(new TestSession( + name: testTask.name, + type: testTask.ext.testType, + module: testTask.ext.testModule, + isPassed: false)) + if (!project.ext.continueOnFailForTestsEnabled) { + throw new GradleException(errorMsg) + } + } +} + +Task reportAllTestsResult = tasks.create ( + name: "reportAllTestsResult", + description: "Report the result all every test that has been run", + type: Task +).with { + doLast { + if (project.ext.testSessions.isEmpty()) { + return + } + + println "\n\n[Test Summary]" + int failedCount = 0 + int totalCount = 0 + project.ext.testSessions.each { session -> + String resultStr + ++totalCount + String logType = "" + if (session.isPassed) { + resultStr = "PASSED" + } else { + resultStr = "FAILED" + logType = "::error::" + ++failedCount + } + println sprintf("%sTest %s %s [%s/%s]", + logType, + session.name, + resultStr, + session.type, + session.module) + } + println "--------------------------------------" + println sprintf("::notice::%s out of %d test(s) passed", totalCount - failedCount, totalCount) + if (failedCount > 0) { + throw new GradleException( + sprintf("%d out of %d test(s) failed", failedCount, totalCount)) + } + } +} + +Task testPackageUploader = createPythonTask( + "testPackageUploader", + "Test the unity_asset_uploader.py application.", + [], + new File(project.ext.unityAssetUploaderDir, "unity_asset_uploader_test.py"), + [], + [], + true).with { task -> + finalizedBy reportAllTestsResult + doLast { + EvaluateTestResult(task) + } + doFirst { + ReportTestStarted(task) + } + setTestProperties(task, TestTypeEnum.PYTHON, TestModuleEnum.TOOL) + } + +createPythonTask( + "testExportUnityPackage", + "Test the export_unity_package.py application", + [], + new File(project.ext.exportUnityPackageDir, "export_unity_package_test.py"), + [], + exportUnityPackageRequirements, + true).with { task -> + setTestProperties(task, TestTypeEnum.PYTHON, TestModuleEnum.TOOL) + finalizedBy reportAllTestsResult + doLast { + EvaluateTestResult(task) + } + doFirst { + ReportTestStarted(task) + } } +createPythonTask( + "testGenGuids", + "Test the gen_guids.py application", + [], + new File(project.ext.exportUnityPackageDir, "gen_guids_test.py"), + [], + ["absl-py"], + true).with { task -> + setTestProperties(task, TestTypeEnum.PYTHON, TestModuleEnum.TOOL) + finalizedBy reportAllTestsResult + doLast { + EvaluateTestResult(task) + } + doFirst { + ReportTestStarted(task) + } + } + +createPythonTask( + "testImportUnityPackage", + "Test the import_unity_package.py application", + [], + new File(project.ext.importUnityPackageDir, "import_unity_package_test.py"), + [], + ["absl-py"], + true).with { task -> + setTestProperties(task, TestTypeEnum.PYTHON, TestModuleEnum.TOOL) + finalizedBy reportAllTestsResult + doLast { + EvaluateTestResult(task) + } + doFirst { + ReportTestStarted(task) + } + } + task updateEmbeddedGradleWrapper(type: Zip) { description "Update the gradle wrapper in gradle-template.zip" from project.ext.scriptDirectory @@ -1021,7 +1894,7 @@ task updateEmbeddedGradleWrapper(type: Zip) { include "gradle/**" archiveName "gradle-template.zip" destinationDir (new File(project.ext.scriptDirectory, - "source/PlayServicesResolver/scripts")) + "source/AndroidResolver/scripts")) } Task buildVersionHandler = createBuildPluginDllTask( @@ -1030,13 +1903,14 @@ Task buildVersionHandlerImpl = createBuildPluginDllTask( "VersionHandlerImpl", "VersionHandlerImpl", "Google.VersionHandlerImpl.dll", true, [buildVersionHandler]) Task buildAndroidResolver = createBuildPluginDllTask( - "AndroidResolver", "PlayServicesResolver", "Google.JarResolver.dll", true, + "AndroidResolver", "AndroidResolver", "Google.JarResolver.dll", true, [updateEmbeddedGradleWrapper, buildVersionHandlerImpl]) Task buildIosResolver = createBuildPluginDllTask( "IosResolver", "IOSResolver", "Google.IOSResolver.dll", true, [buildAndroidResolver]) -Task buildPackageManager = createBuildPluginDllTask( - "PackageManager", "PackageManager", "Google.PackageManager.dll", true, +Task buildPackageManagerResolver = createBuildPluginDllTask( + "PackageManagerResolver", "PackageManagerResolver", + "Google.PackageManagerResolver.dll", true, [buildVersionHandlerImpl]) task preparePluginStagingAreaDir(type: Task) { @@ -1060,17 +1934,42 @@ task preparePluginStagingAreaDir(type: Task) { Task copyPluginTemplateToStagingArea = createCopyFilesTask( "copyPluginTemplateToStagingArea", "Copy the template project into the Unity plugin packaging dir.", - [new File("Assets", "PlayServicesResolver.meta"), - new File(new File("Assets", "PlayServicesResolver"), "Editor.meta")], + [new File("Assets", "ExternalDependencyManager.meta"), + new File(new File("Assets", "ExternalDependencyManager"), "Editor.meta"), + new File(new File(new File("Assets", "ExternalDependencyManager"), "Editor"), + "CHANGELOG.md.meta"), + new File(new File(new File("Assets", "ExternalDependencyManager"), "Editor"), + "LICENSE.meta"), + new File(new File(new File("Assets", "ExternalDependencyManager"), "Editor"), + "README.md.meta"), + new File("Assets", "PlayServicesResolver.meta"), + new File(new File("Assets", "PlayServicesResolver"), "Editor.meta"), + new File(new File(new File("Assets", "PlayServicesResolver"), "Editor"), + "play-services-resolver_v1.2.137.0.txt"), + new File(new File(new File("Assets", "PlayServicesResolver"), "Editor"), + "play-services-resolver_v1.2.137.0.txt.meta")], project.ext.pluginTemplateDir, project.ext.pluginStagingAreaDir, [preparePluginStagingAreaDir], { sourceFile, targetFile -> copyAssetMetadataFile(sourceFile, targetFile) }) +Task copyDocumentationToStagingArea = createCopyFilesTask( + "copyDocumentationToStagingArea", + "Copy documentation into the Unity plugin packaging dir.", + [new File("CHANGELOG.md"), + new File("LICENSE"), + new File("README.md")], + project.ext.scriptDirectory, + new File(new File(new File(project.ext.pluginStagingAreaDir, "Assets"), + "ExternalDependencyManager"), "Editor"), + [preparePluginStagingAreaDir], + { sourceFile, targetFile -> copyAssetMetadataFile(sourceFile, targetFile) }) + Iterable copyComponentsToStagingAreaTasks = [ copyAndroidResolverStaging, copyVersionHandlerStaging, copyVersionHandlerImplStaging, - copyIosResolverStaging].collect { + copyIosResolverStaging, + copyPackageManagerResolverStaging].collect { task -> createCopyFilesTask( sprintf("copy%sToStagingArea", task.ext.componentName), @@ -1095,6 +1994,7 @@ task copyPluginComponentsToStagingArea( task generatePluginManifest(dependsOn: [preparePluginStagingAreaDir, copyPluginTemplateToStagingArea, + copyDocumentationToStagingArea, copyPluginComponentsToStagingArea]) { String unversionedManifestName = currentPluginBasename + ".txt" File outputDir = new File(project.ext.pluginStagingAreaDir, @@ -1104,9 +2004,15 @@ task generatePluginManifest(dependsOn: [preparePluginStagingAreaDir, project.ext.pluginEditorDllDir.path), unversionedManifestName + project.ext.unityMetadataExtension) File manifestFile = versionedAssetFile( - new File(outputDir, unversionedManifestName)) + new File(outputDir, unversionedManifestName), + true, + "_manifest", + false) File manifestMetadataFile = versionedAssetFile( - new File(outputDir, manifestMetadataTemplateFile.name)) + new File(outputDir, manifestMetadataTemplateFile.name), + true, + "_manifest", + false) description "Generate a manifest for the files in the plug-in." inputs.files files(manifestMetadataTemplateFile) @@ -1131,24 +2037,71 @@ task generatePluginManifest(dependsOn: [preparePluginStagingAreaDir, } } -Task buildPlugin = createUnityTask( - "buildPlugin", "build_plugin", [generatePluginManifest], +// Deprecated target for packaging the plugin. +Task buildPluginWithUnity = createUnityTask( + "buildPluginWithUnity", "build_plugin", [generatePluginManifest], project.ext.pluginStagingAreaDir.name, project.ext.buildDir, ["-g.building", "-buildTarget", "android", - "-exportPackage", project.ext.pluginAssetsDir.path, - project.ext.pluginExportFile.absolutePath, + "-exportPackage"] + (project.ext.pluginExportDirs.collect { it.path }) + + [project.ext.pluginExportFile.absolutePath, "-gvh_disable", - "-quit"], true) -buildPlugin.with { - description "Exports the plugin staging area directory as a Unity package." + "-quit"], true, null) +buildPluginWithUnity.with { + description ("(Deprecated) Exports the plugin staging area directory as " + + "a Unity package.") inputs.files files(copyPluginTemplateToStagingArea.outputs.files, copyPluginComponentsToStagingArea.outputs.files) outputs.files files(project.ext.pluginExportFile) } -task releasePlugin(dependsOn: buildPlugin) { +Task buildPlugin = createExportUnityPackageTask( + "buildPlugin", + "Package the .unitypackage with export_unity_package.py.", + [generatePluginManifest], + new File(project.ext.scriptDirectory, "export_unity_package_config.json"), + new File(project.ext.scriptDirectory, "export_unity_package_guids.json"), + new File(project.ext.pluginStagingAreaDir, "Assets"), + true, // Enable .unitypackage export. + false, // Disable UPM export. + project.ext.pluginVersion, + project.ext.pluginExportFile.parentFile, + ["--enabled_sections", "unitypackage documentation"]) +buildPlugin.with { + outputs.files project.ext.pluginExportFile.absolutePath +} + +// Guid paths for UPM package. +File upmPluginPackageDir = new File(currentPluginUpmPackageName, "ExternalDependencyManager") +File upmPluginEditorDir = new File(upmPluginPackageDir, "Editor") +File upmPluginDllDir = new File(upmPluginEditorDir, project.ext.pluginVersion) + +Task genGuidUpm = createGenGuidTask( + "genGuidUpm", + "Generate GUID for .tgz packaging.", + new File(project.ext.scriptDirectory, "export_unity_package_guids.json"), + project.ext.pluginVersion, + [upmPluginDllDir.path] +) + +Task buildUpmPlugin = createExportUnityPackageTask( + "buildUpmPlugin", + "Package the .tgz with export_unity_package.py.", + [generatePluginManifest, genGuidUpm], + new File(project.ext.scriptDirectory, "export_unity_package_config.json"), + new File(project.ext.scriptDirectory, "export_unity_package_guids.json"), + new File(project.ext.pluginStagingAreaDir, "Assets"), + false, // Disable .unitypackage export. + true, // Enable UPM export. + project.ext.pluginVersion, + project.ext.pluginUpmExportFile.parentFile, + []) +buildUpmPlugin.with { + outputs.files project.ext.pluginUpmExportFile.absolutePath +} + +task releasePlugin(dependsOn: [buildPlugin, buildUpmPlugin]) { Map pluginTemplateFilesMap = files( copyPluginTemplateToStagingArea.outputs.files, copyPluginComponentsToStagingArea.outputs.files).collectEntries { @@ -1171,6 +2124,7 @@ task releasePlugin(dependsOn: buildPlugin) { fileTree(dir: project.ext.pluginExplodedDir), pluginTemplateFilesMap.values()) doLast { + // Delete and regenerate built .unitypackage in the repo. delete fileTree( dir: project.ext.pluginReleaseFile.parentFile, includes: [project.ext.currentPluginBasename + "-*.unitypackage"]) @@ -1179,11 +2133,29 @@ task releasePlugin(dependsOn: buildPlugin) { into project.ext.pluginReleaseFile.parentFile rename { src_filename -> project.ext.pluginReleaseFile.name } } + copy { + from project.ext.pluginExportFile + into project.ext.pluginReleaseFileUnversioned.parentFile + rename { src_filename -> project.ext.pluginReleaseFileUnversioned.name } + } + // Delete and regenerate the exploded plugin folder in the repo. delete fileTree(dir: project.ext.pluginExplodedDir) copy { from project.ext.pluginStagingAreaDir into project.ext.pluginExplodedDir - include (project.ext.pluginAssetsDir.path + "/**/*") + include project.ext.pluginExportDirs.collect { + it.path + "/**/*" + } + } + // Delete and regenerate the UPM package in the repo. + delete fileTree(dir: project.ext.pluginUpmDir) + copy { + // Rename the top-level package folder to upm. + eachFile { + path = path.replaceFirst(/^.+?\//, "upm/") + } + from tarTree(project.ext.pluginUpmExportFile) + into project.ext.scriptDirectory } pluginTemplateFilesMap.each { sourceFile, targetFile -> copyFile(sourceFile, targetFile) @@ -1204,10 +2176,14 @@ task gitAddReleaseFilesToStaging(type: Exec, dependsOn: releasePlugin) { */ String readVersionSummaryFromChangelog() { String versionSummary = "" - for (String line in project.ext.changelog.text.split("\n")) { + Boolean foundVersion = false + for (String line in project.ext.changelog.text.tokenize("\n")) { String trimmedLine = line.trim() - if (line.isEmpty()) break - versionSummary += line + "\n" + if (line =~ /^# Version/) { + if (foundVersion) break + foundVersion = true + } + versionSummary += line.replace("#", "-") + "\n" } return versionSummary } @@ -1219,7 +2195,7 @@ String readVersionSummaryFromChangelog() { */ String createCommitMessage() { return sprintf("Version %s\n\n%s", - project.ext.pluginVersionSemVer, + project.ext.pluginVersion, readVersionSummaryFromChangelog()) } @@ -1241,21 +2217,21 @@ task gitCreateReleaseCommit(dependsOn: gitAddReleaseFilesToStaging) { } } -task gitTagRelease(type: Exec, dependsOn: gitCreateReleaseCommit) { +task gitTagRelease(type: Exec) { description ("Run git to tag a release. This should be performed " + "*after* a release commit has been be created.") commandLine "git", "tag", "-a", - sprintf("v%s", pluginVersionSemVer), "-m", + sprintf("v%s", pluginVersion), "-m", sprintf("%s\n\nDownload [here](%s)", createCommitMessage(), sprintf("https://github.com/googlesamples/" + "unity-jar-resolver/raw/v%s/" + - "play-services-resolver-%s.unitypackage", - project.ext.pluginVersionSemVer, + "external-dependency-manager-%s.unitypackage", + project.ext.pluginVersion, project.ext.pluginVersion)) } -// TODO: Version Handler tests to implement in both batch and interactive modes +// TODO: Version Handler tests // - Per platform targeting (iOS, Android, Editor, desktop) // - Version enablement, import newer plugin on top of older plugin and validate // new plugin is enabled, old plugin is removed. @@ -1265,7 +2241,7 @@ task gitTagRelease(type: Exec, dependsOn: gitCreateReleaseCommit) { // - Import plugin into a project and wait for asset processor to enable it. // - Switch .NET version, validate DLLs are enabled / disabled as expected. -// TODO: Android Resolver tests to implement in both batch and interactive modes +// TODO: Android Resolver tests // - Resolve with: // - Conflicting dependencies // - FAT ABI vs. single ABI selection @@ -1278,28 +2254,163 @@ task gitTagRelease(type: Exec, dependsOn: gitCreateReleaseCommit) { // - Project export // - Add a pod which changes the target SDK +createUnityNUnitTest( + "testVersionHandlerImplNUnitTests", + "Runs NUnit tests for the VersionHandlerImpl module.", + [], + new File(project.ext.scriptDirectory, + "source/VersionHandlerImpl/unit_tests"), [], + TestTypeEnum.NUNIT, TestModuleEnum.VERSIONHANDLER +) + +createUnityNUnitTest( + "testPackageManagerResolverNUnitTests", + "Runs NUnit tests for the PackageManagerResolver module.", + [], + new File(project.ext.scriptDirectory, + "source/PackageManagerResolver/unit_tests"), [], + TestTypeEnum.NUNIT, TestModuleEnum.PACKAGEMANAGER +) + createUnityTestBatchAndNonBatch( "testVersionHandlerActivation", ("Imports the plugin into a Unity project and verifies all " + "components can be activated by the Version Handler."), [buildPlugin], - new File("source/VersionHandlerImpl/test/activation"), []) + new File(project.ext.scriptDirectory, + "source/VersionHandlerImpl/test/activation"), + [], [], null, TestTypeEnum.INTEGRATION, TestModuleEnum.VERSIONHANDLER) + +// Launch the test via a script that runs a local web server. +createUnityTestBatchAndNonBatch( + "testVersionHandlerWebRequest", + ("Imports the plugin into a Unity project tests the PortableWebRequest " + + "class."), + [buildPlugin], + new File(project.ext.scriptDirectory, + "source/VersionHandlerImpl/test/webrequest"), + [], [], + { String name, String description, Iterable depends, + File executable, Iterable args, Boolean continueOnFail -> + Iterable runnerArgs = [executable.absolutePath] + args + return createPythonTask( + name, description, depends, + new File( + new File( + new File(project.ext.pluginSourceDir, + "VersionHandlerImpl"), + "test"), + "webrequest_launcher.py"), + runnerArgs, + [], continueOnFail) + }, TestTypeEnum.INTEGRATION, TestModuleEnum.CORE) createUnityTestBatchAndNonBatch( "testVersionHandlerReflection", ("Imports the plugin into a Unity project and tests reflection " + "methods."), [buildPlugin], - new File("source/VersionHandler/test/reflection"), []) + new File(project.ext.scriptDirectory, + "source/VersionHandler/test/reflection"), + [], + [], + null, + TestTypeEnum.INTEGRATION, TestModuleEnum.CORE) + +Task compileIntegrationTester = createXbuildTask( + "compileIntegrationTester", + "Compile Integration Tester module.", + project.ext.pluginSolutionFile, + "IntegrationTester", + fileTree(new File(project.ext.pluginSourceDir, + "IntegrationTester")), + new File(project.ext.buildDir, "IntegrationTester"), + [new File("Google.IntegrationTester.dll")], + [compileVersionHandler]) -createUnityTestBatchAndNonBatch( - "testAndroidResolverAsyncResolve", +/* + * Creates a task which compiles and run a Unity integration test. + * + * @param taskName Name of the test. + * @param description Description of the task. + * @param dependsOn Dependencies of the new task. + * @param integrationTestProject Project within + * project.ext.pluginSolutionFile that contains the integration test. + * @param integrationTestProjectSources Source files integrationTestProject + * requires. + * @param integrationTestProjectOutputs Assemblies generated by the + * integrationTestProject. + * @param unityProjectDir Directory containing a assets to copy into the + * integration test project. + * @param arguments Additional arguments for Unity when running the integration + * test. + * @param testType Type of the test + * @param testModule Module of the test + */ +Task createUnityIntegrationTest(String taskName, + String description, + Iterable dependsOn, + String integrationTestProject, + Iterable integrationTestProjectSources, + Iterable integrationTestProjectOutputs, + File unityProjectDir, + Iterable arguments, + TestTypeEnum testType, + TestModuleEnum testModule) { + Task compileIntegrationTest = createXbuildTask( + sprintf("compile%s", integrationTestProject), + sprintf("Compile %s for %s", integrationTestProject, taskName), + project.ext.pluginSolutionFile, + integrationTestProject, + integrationTestProjectSources, + new File(project.ext.buildDir, integrationTestProject), + integrationTestProjectOutputs, + [compileIntegrationTester] + dependsOn) + compileIntegrationTest.with { task -> + setTestProperties(task, testType, testModule) + } + + createUnityTestBatchAndNonBatch( + taskName, + description, + [buildPlugin], + unityProjectDir, + compileIntegrationTest.outputs.files + + compileIntegrationTester.outputs.files, + arguments, null, testType, testModule) +} + +createUnityIntegrationTest( + "testAndroidResolverIntegrationTests", ("Imports the plugin into a Unity project, runs Android resolution with " + "a combination of dependencies added via the programmatic API and via " + "XML files, verifying all dependencies resolve successfully."), - [buildPlugin], - new File("source/PlayServicesResolver/test/resolve_async"), - ["-buildTarget", "android"]) // TODO: Move this into the test!? + [compileAndroidResolver], + "AndroidResolverIntegrationTests", + fileTree(new File(new File(project.ext.pluginSourceDir, "AndroidResolver"), + "test")), + [new File("Google.AndroidResolverIntegrationTests.dll")], + new File( + project.ext.scriptDirectory, + "source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject"), + ["-buildTarget", "android"], + TestTypeEnum.INTEGRATION, + TestModuleEnum.ANDROIDRESOLVER) + +createUnityIntegrationTest( + "testPackageManagerClientIntegrationTests", + ("Imports the plugin into a Unity project and uses the Unity Package " + + "Manager client to list, search, install and remove packages. "), + [compilePackageManagerResolver], + "PackageManagerClientIntegrationTests", + fileTree(new File(new File(new File(project.ext.pluginSourceDir, + "PackageManagerResolver"), "test"), + "PackageManagerClientIntegrationTests")), + [new File("Google.PackageManagerClientIntegrationTests.dll"), + new File("Google.PackageManagerClientIntegrationTests.dll.mdb")], + null, [], + TestTypeEnum.INTEGRATION, + TestModuleEnum.PACKAGEMANAGER) task cleanTests(type: Delete) { description "Clean test directories." @@ -1321,3 +2432,18 @@ project.defaultTasks = ["build", "test", "release", "clean"].collect { task -> task.name.startsWith(topLevelTaskName) }) } + +// Disable tests by filters. +project.tasks.findAll { task -> + if (task.hasProperty("testType") && task.testType != null && + task.hasProperty("testModule") && task.testModule != null) { + if (!shouldTestRunWithFilters(task.testType, task.testModule)) { + println sprintf("DISABLED :%s", task.name) + task.enabled = false + } + if (project.ext.excludeTestsParam.contains(task.name.toLowerCase())) { + println sprintf("DISABLED :%s", task.name) + task.enabled = false + } + } +} diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.PackageManager.dll.mdb.meta b/exploded/Assets/ExternalDependencyManager/Editor.meta similarity index 58% rename from plugin/Assets/PlayServicesResolver/Editor/Google.PackageManager.dll.mdb.meta rename to exploded/Assets/ExternalDependencyManager/Editor.meta index 11fbbef2..4ef59616 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.PackageManager.dll.mdb.meta +++ b/exploded/Assets/ExternalDependencyManager/Editor.meta @@ -1,8 +1,7 @@ fileFormatVersion: 2 -guid: 42b257c30aa34035972469681b592ac3 -labels: -- gvh -timeCreated: 1538009133 +guid: b42aa8acaabecbf943da2892de5e6aeb +folderAsset: yes +timeCreated: 1448926516 licenseType: Pro DefaultImporter: userData: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll new file mode 100644 index 00000000..30030559 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll differ diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll.meta new file mode 100644 index 00000000..707c589b --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: e2d7ea0845de4cf984265d2a444b7aa4 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll +- gvh +- gvhp_targets-editor +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + validateReferences: 0 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb new file mode 100644 index 00000000..493d3b28 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb differ diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb.meta new file mode 100644 index 00000000..e6b1e059 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: baf24db2bf904e729e7796721c09e8ad +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll new file mode 100644 index 00000000..645d5cd3 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll differ diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll.meta similarity index 76% rename from plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.meta rename to exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll.meta index 2a364309..a1398e9f 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.meta +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll.meta @@ -1,9 +1,10 @@ fileFormatVersion: 2 -guid: c646678e3a6a4df5969a38392783d303 +guid: fa49a85d4ba140a0ae21528ed12d174c labels: -- gvh_version-1.2.111.0 +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll - gvh -- gvh_targets-editor +- gvhp_targets-editor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb new file mode 100644 index 00000000..c6eaef49 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb differ diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb.meta new file mode 100644 index 00000000..12826037 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d13c8602d5e14e43b0e92459754c4315 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll new file mode 100644 index 00000000..a4a6590c Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll differ diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll.meta new file mode 100644 index 00000000..3b4fa84b --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: d8bb10c56a0147bc855a6296778e025e +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll +- gvh +- gvhp_targets-editor +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb new file mode 100644 index 00000000..884ce212 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb differ diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb.meta new file mode 100644 index 00000000..ac071adc --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a695eb9f64fe49569a2db0c4246c877d +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll new file mode 100644 index 00000000..8562ef33 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll differ diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl.dll.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll.meta similarity index 75% rename from plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl.dll.meta rename to exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll.meta index 757abd74..7456068f 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl.dll.meta +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll.meta @@ -1,9 +1,10 @@ fileFormatVersion: 2 -guid: fb8efebd770744099a4896ab6818de16 +guid: 5980a684c61d42fbb6b74e2eb3477016 labels: -- gvh_version-1.2.111.0 +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll - gvh -- gvh_targets-editor +- gvhp_targets-editor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb new file mode 100644 index 00000000..ed8cc976 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb differ diff --git a/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb.meta new file mode 100644 index 00000000..da5b09b2 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9f56badf3ca84753b00163c3b632d4e5 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/CHANGELOG.md b/exploded/Assets/ExternalDependencyManager/Editor/CHANGELOG.md new file mode 100644 index 00000000..e1294a3a --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/CHANGELOG.md @@ -0,0 +1,1430 @@ +# Version 1.2.186 - May 19, 2025 +* iOS Resolver - Set `validateReferences` to off by default, + to prevent errors when running without iOS Support installed. + Fixes #412 and #622 + +# Version 1.2.185 - Feb 3, 2025 +* Android Resolver - Reverse conditional checker for `packaging` keyword in maintemplate based on android gradle plugin version. Fixes #715 + +# Version 1.2.184 - Jan 28, 2025 +* Android Resolver - Update and resolve `packaging` keyword in maintemplate + based on android gradle plugin version. + Fixes #715 + +# Version 1.2.183 - Sep 18, 2024 +* Android Resolver - Handle package paths that don't include a version hash, + which is no longer present with Unity 6. Fixes #697 +* Android Resolver - Handle packages referenced using local file paths. + Fixes #701 + +# Version 1.2.182 - Aug 2, 2024 +* General - Check for gradle version instead of Unity version when determining + the template files to modify. + +# Version 1.2.181 - Jun 26, 2024 +* General - Disable `EditorMeasurement` reporting that relied on the + Measurement Protocol for Universal Analytics. + +# Version 1.2.180 - Jun 4, 2024 +* General - Fix project settings resetting on domain reload. + Fixes #524 + +# Version 1.2.179 - Feb 12, 2024 +* Android Resolver - Added logic to automatically turn on `mainTemplate.gradle` + for new projects, and prompt users to enable it on projects that have previously + had the resolver run. + +# Version 1.2.178 - Dec 20, 2023 +* Added [OpenUPM support](https://openupm.com/packages/com.google.external-dependency-manager/). + +# Version 1.2.177 - Aug 14, 2023 +* iOS Resolver - Added `/opt/homebrew/bin` to Cocoapod executable search path. + Fixes #627 + +# Version 1.2.176 - Apr 27, 2023 +* Android Resolver - Added two Android Resolver settings to determine whether + EDM4U injects custom local Maven repo path as a relative path or full path. + Fixes #537 +* Android Resolver - Inject Maven Repo to `settingTemplate.gradle` from + Unity 2022.2+ + Fixes #594 +* Android Resolver - Jetifier option is enabled by default now. +* Android Resolver - `Explode Aar` option applies to all cases, whether the + project will be exported or not. + Fixes #584 + Fixes #287 + +# Version 1.2.175 - Nov 16, 2022 +* General - Added tvOS podfile support to the iOS resolver. + +# Version 1.2.174 - Oct 06, 2022 +* General - Added tvOS support to the iOS resolver. +* General - Fixed #484 - Changed `EditorMeasurement` to use secure connection. +* Android Resolver - Fixed Android Resolver unable to resolve + `mainTemplate.gradle` in Unity `2022.2+` or `2023.1+`. + +# Version 1.2.173 - Sep 28, 2022 +* General - Added tvOS library support to the export unity package scripts. + +# Version 1.2.172 - Jun 23, 2022 +* iOS Resolver - Stop forcing `ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES` to `YES`, + which seems to cause problem for some when submitting apps. See #526 for more + information. + +# Version 1.2.171 - May 11, 2022 +* iOS Resolver - Change `Enable Swift Framework Support Workaround` setting to + be `ON` by default since more pods are using Swift Framework now. + +# Version 1.2.170 - Apr 4, 2022 +* Android Resolver - Fixes #498 - Fix the path separator of the Maven repo + injected to `mainTemplate.gradle`. +* iOS Resolver - Fixes #470 - Switch default Cocoapods master repo from Github + to CDN. +* iOS Resolver - `Link Framework Statically` setting is now default to `true`. + That is, `use_frameworks! :linkage => static` will be added to `Podfile` by + default instead of `use_frameworks!`. This can be changed in iOS Resolver + settings. This fixes odd behaviors when pods include static libraries, ex. + Firebase Analytics. +* iOS Resolver - Added a workaround when app crashes on launch due to + `Library not loaded: @rpath/libswiftCore.dylib` when some pods includes Swift + framework. This is turned `OFF` by default and can be changed in iOS Resolver + settings. + +# Version 1.2.169 - Jan 20, 2022 +* General - Fixes #425 - Change to save `GvhProjectSettings.xml` without + Unicode byte order mark (BoM). +* Android Resolver - Remove reference to `jcenter()` +* iOS Resolver - Force setting `LANG` when executing Cocoapods in shell mode on + Mac. + +# Version 1.2.168 - Dec 9, 2021 +* All - Fixes #472 by removing the use of `System.Diagnostics.Debug.Assert` +* All - Fixed #477 by properly enabling EDM4U libraries for Unity 2021.2+ when + the package is installed through `.tgz` + +# Version 1.2.167 - Oct 6, 2021 +* All - Moved versioned `.dll` in EDM4U to a versioned folder and remove their + version postfix in their filename. For instance, `IOSResolver.dll` will be + placed at `ExternalDependencyManager/Editor/1.2.167/Google.IOSResolver.dll`. +* Android Resolver - Fixed #243 by only using the highest version in + `mainTemplate.gradle` when duplicated dependencies are presented. +* Android Resolver - Added supports to x86_64 to ABI list for Android apps on + Chrome OS. + +# Version 1.2.166 - Jun 30, 2021 +* All - Fixed #440 and fixed #447 by specifying the parameter type while calling + `GetApplicationIdentifier()` Unity API using reflection, due to a new + overloaded method introduced in Unity 2021.2. +* Android Resolver - Fixed #442 by patching `Dependency.IsGreater()` when the + version strings end '+'. + +# Version 1.2.165 - Apr 28, 2021 +## Bug Fixes +* Version Handler - Fixed #431 by replacing the use of `HttpUtility.UrlEncode()` + which causes NullReferenceException in certain version of Unity. +* Android Resolver - Check that androidSdkRootPath directory exists before using + as sdkPath. +* Android Resolver - Fixed Android Resolver integration tests with Unity + 2019.3+. + +# Version 1.2.164 - Feb 4, 2021 +## New Features +* Android Resolver - Added support for Android packages with classifier in their + namespaces. +* iOS Resolver - Added new settings in iOS Resolver to configure generated + Podfile. +* iOS Resolver - Added a new attribute `addToAllTargets` in Dependencies.xml. + +## Bug Fixes +* iOS Resolver - Fixed XML parsing for `bitcodeEnabled` attribute in + Dependencies.xml. + +# Version 1.2.163 - Dec 15, 2020 +## Bug Fixes +* Version Handler - Fixed measurement reporting + +# Version 1.2.162 - Nov 19, 2020 +## Bug Fixes +* Version Handler - Improved #413 by preventing Version Handler from running + from static constructor when it is disabled. +* Package Manager Resolver - Remove GPR + +# Version 1.2.161 - Oct 12, 2020 +## Bug Fixes +* Android Resolver - Fixed the issue that Android Resolver does not resolve + again before build in Unity 2020 if it failed to resolve previously. + +# Version 1.2.160 - Sep 30, 2020 +## Bug Fixes +* Android Resolver - Fixed a regression that gradleResolver can be null until + Initialize() is called. +* Android Resolver - Fixed a regression that Android Resolver failed in Unity + 2019.3+ due to `gradleTemplate.properties` not enabled when + `mainTemplate.gradle` is not enabled at all. + +# Version 1.2.159 - Sep 11, 2020 +## Bug Fixes +* Android Resolver - Fixed #322 where the Unity editor will lose its target SDK + setting between Unity restarts if `>28` is selected in 2019. This is due to + Unity AndroidSdkVersions enum does not contain values above 28. +* Android Resolver - Fixed #360 where building Android app with Untiy 2019.3+ + may fail due to Jetifier and AndroidX not enabled properly in generated + Gradle project. This fix requires the user to enable + `Custom Gradle Properties Template` found under + `Player Settings > Settings for Android > Publishing Settings`. + +# Version 1.2.158 - Sep 3, 2020 +## Bug Fixes +* Version Handler: Fixed editor freeze when `-executeMethod` is used in + non-batch mode. +* Android Resolver: Normalized file paths when generating local Maven repo + since the path may contains a mix of forward and backward slash on Windows. +* Export Unity Package: Fixed generation of .unitypackage with tarfile on + Windows. + +# Version 1.2.157 - Aug 6, 2020 +## Bug Fixes +* Android Resolver: Delay initialization until active build target is Android + and the editor is not in play mode. +* iOS Resolver: Delay initialization until active build target is iOS + and the editor is not in play mode. +* Export Unity Package: Workaround directory creation racy if multiple export + operations are spawned at the same time. + +# Version 1.2.156 - June 10, 2020 +## Bug Fixes +* Android Resolver: Fixed that the generated local repo assets contains + redundent labels which are causing Version Handler to failed while + uninstalling packages. +* Android Resolver: Fixed that the repo url injected into mainTemplate.gradle + is incorrect when Unity is configured to export gradle project. +* Android Resolver: Limited to only create local Maven repo when the source + repo contains ".srcaar" file. + +## Changes +* All: Described EDM4U analytics data usage in readme. + +# Version 1.2.155 - May 14, 2020 +## Bug Fixes +* All: Fixed compiler error when build with Unity 5.4 or below due to the + usage of Rect.zero. +* All: Ignore cases when checking command line arguments. + +# Version 1.2.154 - May 14, 2020 +## Bug Fixes +* All: Make each MultiSelectWindow for different purposes to have its own + unique window. + +## Changes +* All: Replace all dialog with DialogWindow which is implemented from + EditorWindow. +* Package Manager Resolver: Clarify how manifest.json will be changed in Package + Manager Resolver window. + +# Version 1.2.153 - Apr 24, 2020 +## Bug Fixes +* Android Resolver: Fixed an exception when repainting the Android resolution + window in Unity 2019.3.x. + +# Version 1.2.152 - Apr 17, 2020 +## Bug Fixes +* Version Handler: Fixed exception when waiting for enabled editor DLLs to + load. +* Android Resolver: Fixed regression when using a Custom Gradle Template + on Windows. + +# Version 1.2.151 - Apr 16, 2020 +## Bug Fixes +* Version Handler: When waiting for newly enabled editor DLLs to load, ignore + all DLLs that do not have a file-system location. +* Android Resolver: Fixed resolution when using a Custom Gradle Template with + libraries stored in a local maven repository distributed with a plugin + installed with the Unity Package Manager. + +# Version 1.2.150 - Apr 9, 2020 +## Bug Fixes +* All: The new packaging script when run on MacOS was generating a + .unitypackage archive that could not be read by Unity on Windows. + This release simply repackages the plugin with tar/gzip to fix the problem. + +# Version 1.2.149 - Apr 8, 2020 +## Bug Fixes +* Package Manager Resolver: Fixed spurious error message when resuming + migration after installing a UPM package. + +# Version 1.2.148 - Apr 8, 2020 +## Bug Fixes +* Package Manager Resolver: Fixed an exception when resuming migration + after installing a UPM package. + +# Version 1.2.147 - Apr 8, 2020 +## Bug Fixes +* Version Handler: Fixed alias traversal bug which caused problems when + migrating from installed .unitypackage files to UPM packages. + +# Version 1.2.146 - Apr 8, 2020 +## Bug Fixes +* Version Handler: Fixed exception in manifest parsing when a manifest is + detected with no aliases. + +# Version 1.2.145 - Apr 2, 2020 +## New Features +* Package Manager Resolver: Added a method to migrate Version Handler + managed packages installed via `.unitypackage` to Unity Package Manager + packages. This is initially used to migrate the External Dependency Manager + to UPM. + +## Changes +* All: Verbose logging is now no longer automatically enabled in batch mode + across all components. Instead logging can be configured using each + component's verbose logging setting or by using the `-gvh_log_debug` command + line flag when starting Unity. +* Version Handler: Sped up version handler updates when the app domain isn't + reloaded. + +## Bug Fixes +* Version Handler: Fixed the display of the obsolete files clean up dialog + when the asset database refreshes. +* Version Handler: Improved reliability of callback from + the VersionHandler.UpdateCompleteMethods event when an asset database + refresh occurs. +* Version Handler: Fixed duplicate exportPath labels when 'Assets/' is the + root of paths assigned to files. +* Version Handler: Handle empty lines in manifest files. + +# Version 1.2.144 - Mar 23, 2020 +## Changed +* iOS Resolver: Removed the ability to configure the Xcode target a Cocoapod + is added to. + +## Bug Fixes +* iOS Resolver: Reverted support for adding Cocoapods to multiple targets as + it caused a regression (exception thrown during post-build step) in some + versions of Unity. + +# Version 1.2.143 - Mar 20, 2020 +## Bug Fixes +* Android Resolver: Fixed caching of resolution state which was causing + the resolver to always run when no dependencies had changed. + +# Version 1.2.142 - Mar 19, 2020 +## Changes +* Package Manager Resolver: Enabled auto-add by default. + +# Version 1.2.141 - Mar 19, 2020 +## Bug Fixes +* Fixed a bug when retrieving project settings. If a plugin was configured + to fetch project settings, if a setting was fetched (e.g "foo") and this + setting existed in the system settings but not the project settings the + system value would override the default value leading to unexpected + behavior. +* Fixed a warning when caching web request classes in Unity 5.6. + +# Version 1.2.140 - Mar 19, 2020 +## Bug Fixes +* Fixed measurement reporting in Unity 5.x. +* Version Handler: Fixed NullReferenceException when an asset doesn't have + an AssetImporter. + +# Version 1.2.139 - Mar 18, 2020 +## Changed +* Added documentation to the built plugin. + +# Version 1.2.138 - Mar 17, 2020 +## New Features +* Package Manager Resolver: Added the Package Manager Resolver + component that allows developers to easily boostrap Unity Package Manager + (UPM) registry addition using unitypackage plugins. +* Version Handler: Added a window that allows plugins to managed by the + Version Handler to be uninstalled. +* Version Handler: Added support for displaying installed plugins. +* Version Handler: Added support for moving files in plugins to their install + locations (if the plugin has been configured to support this). +* iOS Resolver: Added the ability to configure the Xcode target a Cocoapod is + added to. + +## Bug Fixes +* Fixed upgrade from version 1.2.137 and below after the plugin rename to + EDM4U broke the upgrade process. +* Android Resolver: Worked around PlayerSettings.Android.targetSdkVersion + returning empty names for some values in 2019.x. +* Version Handler: Fixed the display of the obsolete files clean up window. +* Version Handler: Fixed managed file check when assets are modified in the + project after plugin import. + +# Version 1.2.137 - Mar 6, 2020 +## Changed +* Renamed package to External Package Manager for Unity (EDM4U). + We changed this to reflect what this plugin is doing today which is far more + than the original scope which just consisted of importing jar files from the + Android SDK maven repository. + Scripts that used to pull `play-services-resolver*.unitypackage` will now have + to request `external-dependency-manager*.unitypackage` instead. + We'll still be shipping a `play-services-resolver*_manifest.txt` file to + handle upgrading from older versions of the plugin. + +## New Features +* All Components: Added reporting of usage so that we can remotely detect + errors and target improvements. +* Android Resolver: Added support for *Dependencies.xml files in Unity Package + Manager packages. +* iOS Resolver: Added support for *Dependencies.xml files in Unity Package + Manager packages. + +## Bug Fixes +* Version Handler: Disabled attempts to disable asset metadata modification + when assets are in a Unity Package Manager managed package. + +# Version 1.2.136 - Feb 19, 2019 +## Bug Fixes +* Android Resolver: Fixed OpenJDK path discovery in Unity 2019.3.1. + +# Version 1.2.135 - Dec 5, 2019 +## Bug Fixes +* All Components: Fixed stack overflow when loading project settings. + +# Version 1.2.134 - Dec 4, 2019 +## Bug Fixes +* All Components: Fixed an issue which caused project settings to be cleared + when running in batch mode. + +# Version 1.2.133 - Nov 18, 2019 +## Bug Fixes +* All Components: Failure to save project settings will now report an error + to the log rather than throwing an exception. + +# Version 1.2.132 - Nov 11, 2019 +## Bug Fixes +* Android Resolver: Worked around expansion of DIR_UNITYPROJECT on Windows + breaking Gradle builds when used as part of a file URI. +* Android Resolver: mainTemplate.gradle is only written if it needs to be + modified. + +# Version 1.2.131 - Oct 29, 2019 +## Bug Fixes +* Version Handler: Improved execution of events on the main thread in batch + mode. +* Version Handler: Improved log level configuration at startup. +* Version Handler: Improved performance of class lookup in deferred method + calls. +* Version Handler: Fixed rename to enable / disable for editor assets. +* iOS Resolver: Improved log level configuration at startup. +* Android Resolver: Improved local maven repo path reference in + mainTemplate.gradle using DIR_UNITYPROJECT. DIR_UNITYPROJECT by Unity + to point to the local filesystem path of the Unity project when Unity + generates the Gradle project. + +# Version 1.2.130 - Oct 23, 2019 +## New Features +* iOS Resolver: Added support for modifying the Podfile before `pod install` + is executed. + +## Bug Fixes +* Version Handler: Fixed invalid classname error when calling + `VersionHandler.UpdateVersionedAssets()`. + +# Version 1.2.129 - Oct 2, 2019 +## Bug Fixes +* iOS Resolver: Changed Cocoapod integration in Unity 2019.3+ to + only add Pods to the UnityFramework target. + +# Version 1.2.128 - Oct 1, 2019 +## Bug Fixes +* iOS Resolver: Fixed Cocoapod project integration mode with Unity + 2019.3+. + +# Version 1.2.127 - Sep 30, 2019 +## Changes +* Android Resolver: All Android Resolver settings File paths are now + serialized with POSIX directory separators. + +# Version 1.2.126 - Sep 27, 2019 +## Changes +* Android Resolver: File paths are now serialized with POSIX directory + separators. +## Bug Fixes +* Android Resolver: Fixed resolution when the parent directory of a Unity + project contains a Gradle project (i.e `settings.gradle` file). + +# Version 1.2.125 - Sep 23, 2019 +## Bug Fixes +* All components: Silenced a warning about not being able to set the console + encoding to UTF8. +* Android Resolver: Worked around broken AndroidSDKTools class in some + versions of Unity. +* iOS Resolver: Fixed iOS target SDK version check +* Version Handler: Changed clean up obsolete files window so that it doesn't + exceed the screen size. + +# Version 1.2.124 - Jul 28, 2019 +## Bug Fixes +* All components: Fixed regression with source control integration when using + Unity 2019.1+. + +# Version 1.2.123 - Jul 23, 2019 +## New Features +* All components: Source control integration for project settings. +## Changes +* Android Resolver: Removed AAR cache as it now makes little difference to + incremental resolution performance. +* Android Resolver: Improved embedded resource management so that embedded + resources should upgrade when the plugin is updated without restarting + the Unity editor. +## Bug Fixes +* Version Handler: Fixed InvokeMethod() and InvokeStaticMethod() when calling + methods that have interface typed arguments. + +# Version 1.2.122 - Jul 2, 2019 +## Bug Fixes +* iOS Resolver: Worked around Unity not loading the iOS Resolver DLL as it + referenced the Xcode extension in a public interface. The iOS Resolver + DLL still references the Xcode extension internally and just handles + missing type exceptions dynamically. + +# Version 1.2.121 - Jun 27, 2019 +## Bug Fixes +* Android Resolver: Fixed warning about missing Packages folder when loading + XML dependencies files in versions of Unity without the package manager. +* Android Resolver: Fixed resolution window progress bar exceeding 100%. +* Android Resolver: If AndroidX is detected in the set of resolved libraries, + the user will be prompted to enable the Jetifier. +* Android Resolver: Improved text splitting in text area windows. +* iOS Resolver: Added support for Unity's breaking changes to the Xcode API + in 2019.3.+. Cocoapods are now added to build targets, Unity-iPhone and + UnityFramework in Unity 2019.3+. + +# Version 1.2.120 - Jun 26, 2019 +## New Features +* Android Resolver: Added support for loading *Dependencies.xml files from + Unity Package Manager packages. +* Android Resolver: Resolution window is now closed if resolution runs as + a pre-build step. +* iOS Resolver: Added support for loading *Dependencies.xml files from + Unity Package Manager packages. +## Bug Fixes +* Android Resolver: Fixed generation of relative repo paths when using + mainTemplate.gradle resolver. +* Android Resolver: Fixed copy of .srcaar to .aar files in repos embedded in a + project when a project path has characters (e.g whitespace) that are escaped + during conversion to URIs. +* Android Resolver: Fixed auto-resolution always running if the Android SDK + is managed by Unity Hub. + +# Version 1.2.119 - Jun 19, 2019 +## Bug Fixes +* Android Resolver: Fixed error reported when using Jetifier integration + in Unity 2018+ if the target SDK is set to "highest installed". + +# Version 1.2.118 - Jun 18, 2019 +## New Features +* Android Resolver: Added initial + [Jetifier](https://developer.android.com/studio/command-line/jetifier) + integration which simplifies + [migration](ttps://developer.android.com/jetpack/androidx/migrate) + to Jetpack ([AndroidX](https://developer.android.com/jetpack/androidx)) + libraries in cases where all dependencies are managed by the Android + Resolver. + This can be enabled via the `Use Jetifier` option in the + `Assets > Play Services Resolver > Android Resolver > Settings` menu. + Caveats: + - If your project contains legacy Android Support Library .jar and .aar + files, these files will need to be removed and replaced with references to + artifacts on Maven via `*Dependencies.xml` files so that the Jetifier + can map them to Jetpack (AndroidX) libraries. + For example, remove the file `support-v4-27.0.2.jar` and replace it with + `` in a + `*Dependencies.xml` file. + - If your project contains .jar or .aar files that use the legacy Android + Support Libraries, these will need to be moved into a local Maven repo + [See this guide](https://maven.apache.org/guides/mini/guide-3rd-party-jars-local.html) + and then these files should be removed from your Unity project and instead + referenced via `*Dependencies.xml` files so that the Jetifier can + patch them to reference the Jetpack lirbaries. + +## Bug Fixes +* Android Resolver: Disabled version locking of com.android.support:multidex + does not use the same versioning scheme as other legacy Android support + libraries. +* Version Handler: Made Google.VersionHandler.dll's asset GUID stable across + releases. This faciliates error-free import into projects where + Google.VersionHandler.dll is moved from the default install location. + +# Version 1.2.117 - Jun 12, 2019 +## Bug Fixes +* Android Resolver: Fix copying of .srcaar to .aar files for + mainTemplate.gradle resolution. PluginImporter configuration was previously + not being applied to .aar files unless the Unity project was saved. + +# Version 1.2.116 - Jun 7, 2019 +## Bug Fixes +* Android Resolver: Fixed resolution of Android dependencies without version + specifiers. +* Android Resolver: Fixed Maven repo not found warning in Android Resolver. +* Android Resolver: Fixed Android Player directory not found exception in + Unity 2019.x when the Android Player isn't installed. + +# Version 1.2.115 - May 28, 2019 +## Bug Fixes +* Android Resolver: Fixed exception due to Unity 2019.3.0a4 removing + x86 from the set of supported ABIs. + +# Version 1.2.114 - May 27, 2019 +## New Features +* Android Resolver: Added support for ABI stripping when using + mainTemplate.gradle. This only works with AARs stored in repos + on the local filesystem. + +# Version 1.2.113 - May 24, 2019 +## New Features +* Android Resolver: If local repos are moved, the plugin will search the + project for matching directories in an attempt to correct the error. +* Version Handler: Files can be now targeted to multiple build targets + using multiple "gvh_" asset labels. +## Bug Fixes +* Android Resolver: "implementation" or "compile" are now added correctly + to mainTemplate.gradle in Unity versions prior to 2019. + +# Version 1.2.112 - May 22, 2019 +## New Features +* Android Resolver: Added option to disable addition of dependencies to + mainTemplate.gradle. + See `Assets > Play Services Resolver > Android Resolver > Settings`. +* Android Resolver: Made paths to local maven repositories in + mainTemplate.gradle relative to the Unity project when a project is not + being exported. +## Bug Fixes +* Android Resolver: Fixed builds with mainTemplate.gradle integration in + Unity 2019. +* Android Resolver: Changed dependency inclusion in mainTemplate.gradle to + use "implementation" or "compile" depending upon the version of Gradle + included with Unity. +* Android Resolver: Gracefully handled exceptions if the console encoding + can't be modified. +* Android Resolver: Now gracefully fails if the AndroidPlayer directory + can't be found. + +# Version 1.2.111 - May 9, 2019 +## Bug Fixes +* Version Handler: Fixed invocation of methods with named arguments. +* Version Handler: Fixed occasional hang when the editor is compiling + while activating plugins. + +# Version 1.2.110 - May 7, 2019 +## Bug Fixes +* Android Resolver: Fixed inclusion of some srcaar artifacts in builds with + Gradle builds when using mainTemplate.gradle. + +# Version 1.2.109 - May 6, 2019 +## New Features: +* Added links to documentation from menu. +* Android Resolver: Added option to auto-resolve Android libraries on build. +* Android Resolver: Added support for packaging specs of Android libraries. +* Android Resolver: Pop up a window when displaying Android dependencies. + +## Bug Fixes +* Android Resolver: Support for Unity 2019 Android SDK and JDK install locations +* Android Resolver: e-enable AAR explosion if internal builds are enabled. +* Android Resolver: Gracefully handle exceptions on file deletion. +* Android Resolver: Fixed Android Resolver log spam on load. +* Android Resolver: Fixed save of Android Resolver PromptBeforeAutoResolution + setting. +* Android Resolver: Fixed AAR processing failure when an AAR without + classes.jar is found. +* Android Resolver: Removed use of EditorUtility.DisplayProgressBar which + was occasionally left displayed when resolution had completed. +* Version Handler: Fixed asset rename to disable when a disabled file exists. + +# Version 1.2.108 - May 3, 2019 +## Bug Fixes: +* Version Handler: Fixed occasional hang on startup. + +# Version 1.2.107 - May 3, 2019 +## New Features: +* Version Handler: Added support for enabling / disabling assets that do not + support the PluginImporter, based upon build target selection. +* Android Resolver: Added support for the global specification of maven repos. +* iOS Resolver: Added support for the global specification of Cocoapod sources. + +# Version 1.2.106 - May 1, 2019 +## New Features +* iOS Resolver: Added support for development pods in Xcode project integration + mode. +* iOS Resolver: Added support for source pods with resources in Xcode project + integration mode. + +# Version 1.2.105 - Apr 30, 2019 +## Bug fixes +* Android Resolver: Fixed reference to Java tool path in logs. +* Android and iOS Resolvers: Changed command line execution to emit a warning + rather than throwing an exception and failing, when it is not possible to + change the console input and output encoding to UTF-8. +* Android Resolver: Added menu option and API to delete resolved libraries. +* Android Resolver: Added menu option and API to log the repos and libraries + currently included in the project. +* Android Resolver: If Plugins/Android/mainTemplate.gradle file is present and + Gradle is selected as the build type, resolution will simply patch the file + with Android dependencies specified by plugins in the project. + +# Version 1.2.104 - Apr 10, 2019 +## Bug Fixes +* Android Resolver: Changed Android ABI selection method from using whitelisted + Unity versions to type availability. This fixes an exception on resolution + in some versions of Unity 2017.4. + +# Version 1.2.103 - Apr 2, 2019 +## Bug Fixes +* Android Resolver: Whitelisted Unity 2017.4 and above with ARM64 support. +* Android Resolver: Fixed Java version check to work with Java SE 12 and above. + +# Version 1.2.102 - Feb 13, 2019 +## Bug Fixes +* Android Resolver: Fixed the text overflow on the Android Resolver + prompt before initial run to fit inside the buttons for + smaller screens. + +# Version 1.2.101 - Feb 12, 2019 +## New Features +* Android Resolver: Prompt the user before the resolver runs for the + first time and allow the user to elect to disable from the prompt. +* Android Resolver: Change popup warning when resolver is disabled + to be a console warning. + +# Version 1.2.100 - Jan 25, 2019 +## Bug Fixes +* Android Resolver: Fixed AAR processing sometimes failing on Windows + due to file permissions. + +# Version 1.2.99 - Jan 23, 2019 +## Bug Fixes +* Android Resolver: Improved performance of project property polling. +* Version Handler: Fixed callback of VersionHandler.UpdateCompleteMethods + when the update process is complete. + +# Version 1.2.98 - Jan 9, 2019 +## New Features +* iOS Resolver: Pod declaration properties can now be set via XML pod + references. For example, this can enable pods for a subset of build + configurations. +## Bug Fixes +* iOS Resolver: Fixed incremental builds after local pods support caused + regression in 1.2.96. + +# Version 1.2.97 - Dec 17, 2018 +## Bug Fixes +* Android Resolver: Reduced memory allocation for logic that monitors build + settings when auto-resolution is enabled. If auto-resolution is disabled, + almost all build settings are no longer polled for changes. + +# Version 1.2.96 - Dec 17, 2018 +## Bug Fixes +* Android Resolver: Fixed repacking of AARs to exclude .meta files. +* Android Resolver: Only perform auto-resolution on the first scene while + building. +* Android Resolver: Fixed parsing of version ranges that include whitespace. +* iOS Resolver: Added support for local development pods. +* Version Handler: Fixed Version Handler failing to rename some files. + +# Version 1.2.95 - Oct 23, 2018 +## Bug Fixes: +* Android Resolver: Fixed auto-resolution running in a loop in some scenarios. + +# Version 1.2.94 - Oct 22, 2018 +## Bug Fixes +* iOS Resolver: Added support for PODS_TARGET_SRCROOT in source Cocoapods. + +# Version 1.2.93 - Oct 22, 2018 +## Bug Fixes +* Android Resolver: Fixed removal of Android libraries on auto-resolution when + `*Dependencies.xml` files are deleted. + +# Version 1.2.92 - Oct 2, 2018 +## Bug Fixes +* Android Resolver: Worked around auto-resolution hang on Windows if + resolution starts before compilation is finished. + +# Version 1.2.91 - Sep 27, 2018 +## Bug Fixes +* Android Resolver: Fixed Android Resolution when the selected build target + isn't Android. +* Added C# assembly symbols the plugin to simplify debugging bug reports. + +# Version 1.2.90 - Sep 21, 2018 +## Bug Fixes +* Android Resolver: Fixed transitive dependency selection of version locked + packages. + +# Version 1.2.89 - Aug 31, 2018 +## Bug Fixes +* Fixed FileLoadException in ResolveUnityEditoriOSXcodeExtension an assembly + can't be loaded. + +# Version 1.2.88 - Aug 29, 2018 +## Changed +* Improved reporting of resolution attempts and conflicts found in the Android + Resolver. +## Bug Fixes +* iOS Resolver now correctly handles sample code in CocoaPods. Previously it + would add all sample code to the project when using project level + integration. +* Android Resolver now correctly handles Gradle conflict resolution when the + resolution results in a package that is compatible with all requested + dependencies. + +# Version 1.2.87 - Aug 23, 2018 +## Bug Fixes +* Fixed Android Resolver "Processing AARs" dialog getting stuck in Unity 5.6. + +# Version 1.2.86 - Aug 22, 2018 +## Bug Fixes +* Fixed Android Resolver exception in OnPostProcessScene() when the Android + platform isn't selected. + +# Version 1.2.85 - Aug 17, 2018 +## Changes +* Added support for synchronous resolution in the Android Resolver. + PlayServicesResolver.ResolveSync() now performs resolution synchronously. +* Auto-resolution in the Android Resolver now results in synchronous resolution + of Android dependencies before the Android application build starts via + UnityEditor.Callbacks.PostProcessSceneAttribute. + +# Version 1.2.84 - Aug 16, 2018 +## Bug Fixes +* Fixed Android Resolver crash when the AndroidResolverDependencies.xml + file can't be written. +* Reduced log spam when a conflicting Android library is pinned to a + specific version. + +# Version 1.2.83 - Aug 15, 2018 +## Bug Fixes +* Fixed Android Resolver failures due to an in-accessible AAR / JAR explode + cache file. If the cache can't be read / written the resolver now continues + with reduced performance following recompilation / DLL reloads. +* Fixed incorrect version number in plugin manifest on install. + This was a minor issue since the version handler rewrote the metadata + after installation. + +# Version 1.2.82 - Aug 14, 2018 +## Changed +* Added support for alphanumeric versions in the Android Resolver. + +## Bug Fixes +* Fixed Android Resolver selection of latest duplicated library. +* Fixed Android Resolver conflict resolution when version locked and non-version + locked dependencies are specified. +* Fixed Android Resolver conflict resolution when non-existent artifacts are + referenced. + +# Version 1.2.81 - Aug 9, 2018 +## Bug Fixes +* Fixed editor error that would occur when when + `PlayerSettings.Android.targetArchitectures` was set to + `AndroidArchitecture.All`. + +# Version 1.2.80 - Jul 24, 2018 +## Bug Fixes +* Fixed project level settings incorrectly falling back to system wide settings + when default property values were set. + +# Version 1.2.79 - Jul 23, 2018 +## Bug Fixes +* Fixed AndroidManifest.xml patching on Android Resolver load in Unity 2018.x. + +# Version 1.2.78 - Jul 19, 2018 +## Changed +* Added support for overriding conflicting dependencies. + +# Version 1.2.77 - Jul 19, 2018 +## Changed +* Android Resolver now supports Unity's 2018 ABI filter (i.e arm64-v8a). +* Reduced Android Resolver build option polling frequency. +* Disabled Android Resolver auto-resolution in batch mode. Users now need + to explicitly kick off resolution through the API. +* All Android Resolver and Version Handler dialogs are now disabled in batch + mode. +* Verbose logging for all plugins is now enabled by default in batch mode. +* Version Handler bootstrapper has been improved to no longer call + UpdateComplete multiple times. However, since Unity can still reload the + app domain after plugins have been enabled, users still need to store their + plugin state to persistent storage to handle reloads. + +## Bug Fixes +* Android Resolver no longer incorrectly adds MANIFEST.MF files to AARs. +* Android Resolver auto-resolution jobs are now unscheduled when an explicit + resolve job is started. + +# Version 1.2.76 - Jul 16, 2018 +## Bug Fixes +* Fixed variable replacement in AndroidManifest.xml files in the Android + Resolver. + Version 1.2.75 introduced a regression which caused all variable replacement + to replace the *entire* property value rather than the component of the + property that referenced a variable. For example, + given "applicationId = com.my.app", "${applicationId}.foo" would be + incorrectly expanded as "com.my.app" rather than "com.my.app.foo". This + resulted in numerous issues for Android builds where content provider + initialization would fail and services may not start. + +## Changed +* Gradle prebuild experimental feature has been removed from the Android + Resolver. The feature has been broken for some time and added around 8MB + to the plugin size. +* Added better support for execution of plugin components in batch mode. + In batch mode UnityEditor.update is sometimes never called - like when a + single method is executed - so the new job scheduler will execute all jobs + synchronously from the main thread. + +# Version 1.2.75 - Jun 20, 2018 +## New Features +* Android Resolver now monitors the Android SDK path when + auto-resolution is enabled and triggers resolution when the path is + modified. + +## Changed +* Android auto-resolution is now delayed by 3 seconds when the following build + settings are changed: + - Target ABI. + - Gradle build vs. internal build. + - Project export. +* Added a progress bar display when AARs are being processed during Android + resolution. + +## Bug Fixes +* Fixed incorrect Android package version selection when a mix of + version-locked and non-version-locked packages are specified. +* Fixed non-deterministic Android package version selection to select + the highest version of a specified package rather than the last + package specification passed to the Gradle resolution script. + +# Version 1.2.74 - Jun 19, 2018 +## New Features +* Added workaround for broken AndroidManifest.xml variable replacement in + Unity 2018.x. By default ${applicationId} variables will be replaced by + the bundle ID in the Plugins/Android/AndroidManifest.xml file. The + behavior can be disabled via the Android Resolver settings menu. + +# Version 1.2.73 - May 30, 2018 +## Bug Fixes +* Fixed spurious warning message about missing Android plugins directory on + Windows. + +# Version 1.2.72 - May 23, 2018 +## Bug Fixes +* Fixed spurious warning message about missing Android plugins directory. + +# Version 1.2.71 - May 10, 2018 +## Bug Fixes +* Fixed resolution of Android dependencies when the `Assets/Plugins/Android` + directory is named in a different case e.g `Assets/plugins/Android`. + +# Version 1.2.70 - May 7, 2018 +## Bug Fixes +* Fixed bitcode flag being ignored for iOS pods. + +# Version 1.2.69 - May 7, 2018 +## Bug Fixes +* Fixed escaping of local repository paths in Android Resolver. + +# Version 1.2.68 - May 3, 2018 +## Changes +* Added support for granular builds of Google Play Services. + +# Version 1.2.67 - May 1, 2018 +## Changes +* Improved support for iOS source-only pods in Unity 5.5 and below. + +# Version 1.2.66 - April 27, 2018 +## Bug Fixes +* Fixed Version Handler renaming of Linux libraries with hyphens in filenames. + Previously, libraries named Foo-1.2.3.so were not being renamed to + libFoo-1.2.3.so on Linux which could break native library loading on some + versions of Unity. + +# Version 1.2.65 - April 26, 2018 +## Bug Fixes +* Fix CocoaPods casing in logs and comments. + +# Version 1.2.64 - Mar 16, 2018 +## Bug Fixes +* Fixed bug in download_artifacts.gradle (used by Android Resolver) which + reported a failure if required artifacts already exist. + +# Version 1.2.63 - Mar 15, 2018 +## Bug Fixes +* Fixed iOS Resolver include search paths taking precedence over system headers + when using project level resolution. +* Fixed iOS Resolver includes relative to library root, when using project level + resolution. + +# Version 1.2.62 - Mar 12, 2018 +## Changes +* Improved error reporting when a file can't be moved to trash by the + Version Handler. +## Bug Fixes +* Fixed Android Resolver throwing NullReferenceException when the Android SDK + path isn't set. +* Fixed Version Handler renaming files with underscores if the + "Rename to Canonical Filenames" setting is enabled. + +# Version 1.2.61 - Jan 22, 2018 +## Bug Fixes +* Fixed Android Resolver reporting non-existent conflicting dependencies when + Gradle build system is enabled. + +# Version 1.2.60 - Jan 12, 2018 +## Changes +* Added support for Maven / Ivy version specifications for Android packages. +* Added support for Android SNAPSHOT packages. + +## Bug Fixes +* Fixed Openjdk version check. +* Fixed non-deterministic Android package resolution when two packages contain + an artifact with the same name. + +# Version 1.2.59 - Oct 19, 2017 +## Bug Fixes +* Fixed execution of Android Gradle resolution script when it's located + in a path with whitespace. + +# Version 1.2.58 - Oct 19, 2017 +## Changes +* Removed legacy resolution method from Android Resolver. + It is now only possible to use the Gradle or Gradle prebuild resolution + methods. + +# Version 1.2.57 - Oct 18, 2017 +## Bug Fixes +* Updated Gradle wrapper to 4.2.1 to fix issues using Gradle with the + latest Openjdk. +* Android Gradle resolution now also uses gradle.properties to pass + parameters to Gradle in an attempt to workaround problems with + command line argument parsing on Windows 10. + +# Version 1.2.56 - Oct 12, 2017 +## Bug Fixes +* Fixed Gradle artifact download with non-version locked artifacts. +* Changed iOS resolver to only load dependencies at build time. + +# Version 1.2.55 - Oct 4, 2017 +## Bug Fixes +* Force Android Resolution when the "Install Android Packages" setting changes. + +# Version 1.2.54 - Oct 4, 2017 +## Bug Fixes +* Fixed execution of command line tools on Windows when the path to the tool + contains a single quote (apostrophe). In this case we fallback to executing + the tool via the system shell. + +# Version 1.2.53 - Oct 2, 2017 +## New Features +* Changed Android Resolver "resolution complete" dialog so that it now displays + failures. +* Android Resolver now detects conflicting libraries that it does not manage + warning the user if they're newer than the managed libraries and prompting + the user to clean them up if they're older or at the same version. + +## Bug Fixes +* Improved Android Resolver auto-resolution speed. +* Fixed bug in the Gradle Android Resolver which would result in resolution + succeeding when some dependencies are not found. + +# Version 1.2.52 - Sep 25, 2017 +## New Features +* Changed Android Resolver's Gradle resolution to resolve conflicting + dependencies across Google Play services and Android Support library packages. + +# Version 1.2.51 - Sep 20, 2017 +## Changes +* Changed iOS Resolver to execute the CocoaPods "pod" command via the shell + by default. Some developers customize their shell environment to use + custom ssh certs to access internal git repositories that host pods so + executing "pod" via the shell will work for these scenarios. + The drawback of executing "pod" via the shell could potentially cause + users problems if they break their shell environment. Though users who + customize their shell environments will be able to resolve these issues. + +# Version 1.2.50 - Sep 18, 2017 +## New Features +* Added option to disable the Gradle daemon in the Android Resolver. + This daemon is now disabled by default as some users are getting into a state + where multiple daemon instances are being spawned when changing dependencies + which eventually results in Android resolution failing until all daemon + processes are manually killed. + +## Bug Fixes +* Android resolution is now always executed if the user declines the update + of their Android SDK. This ensure users can continue to use out of date + Android SDK packages if they desire. + +# Version 1.2.49 - Sep 18, 2017 +## Bug Fixes +* Removed modulemap parsing in iOS Resolver. + The framework *.modulemap did not need to be parsed by the iOS Resolver + when injecting Cocoapods into a Xcode project. Simply adding a modular + framework to a Xcode project results in Xcode's Clang parsing the associated + modulemap and injecting any compile and link flags into the build process. + +# Version 1.2.48 - Sep 12, 2017 +## New Features +* Changed settings to be per-project by default. + +## Bug Fixes +* Added Google maven repository to fix GradlePrebuild resolution with Google + components. +* Fixed Android Resolution failure with spaces in paths. + +# Version 1.2.47 - Aug 29, 2017 +## New Features +* Android and iOS dependencies can now be specified using *Dependencies.xml + files. This is now the preferred method for registering dependencies, + we may remove the API for dependency addition in future. +* Added "Reset to Defaults" button to each settings dialog to restore default + settings. +* Android Resolver now validates the configured JDK is new enough to build + recently released Android libraries. +## Bug Fixes +* Fixed a bug that caused dependencies with the "LATEST" version specification + to be ignored when using the Gradle mode of the Android Resolver. +* Fixed a race condition when running Android Resolution. +* Fixed Android Resolver logging if a PlayServicesSupport instance is created + with no logging enabled before the Android Resolver is initialized. +* Fixed iOS resolver dialog in Unity 4. +* Fixed iOS Cocoapod Xcode project integration in Unity 4. + +# Version 1.2.46 - Aug 22, 2017 +## Bug Fixes +* GradlePrebuild Android resolver on Windows now correctly locates dependent + data files. + +# Version 1.2.45 - Aug 22, 2017 +## Bug Fixes +* Improved Android package auto-resolution and fixed clean up of stale + dependencies when using Gradle dependency resolution. + +# Version 1.2.44 - Aug 21, 2017 +## Bug Fixes +* Enabled autoresolution for Gradle Prebuild. +* Made the command line dialog windows have selectable text. +* Fixed incorrect "Android Settings" dialog disabled groups. +* Updated PlayServicesResolver android platform detection to use the package + manager instead of the 'android' tool. +* UnityCompat reflection methods 'GetAndroidPlatform' and + 'GetAndroidBuildToolsVersion' are now Obsolete due to dependence on the + obsolete 'android' build tool. + +# Version 1.2.43 - Aug 18, 2017 +## Bug Fixes +* Fixed Gradle resolution in the Android Resolver when running + PlayServicesResolver.Resolve() in parallel or spawning multiple + resolutions before the previous resolve completed. + +# Version 1.2.42 - Aug 17, 2017 +## Bug Fixes +* Fixed Xcode project level settings not being applied by IOS Resolver when + Xcode project pod integration is enabled. + +# Version 1.2.41 - Aug 15, 2017 +## Bug Fixes +* IOS Resolver's Xcode workspace pod integration is now disabled when Unity + Cloud Build is detected. Unity Cloud Build does not follow the same build + process as the Unity editor and fails to open the generated xcworkspace at + this time. + +# Version 1.2.40 - Aug 15, 2017 +## Bug Fixes +* Moved Android Resolver Gradle Prebuild scripts into Google.JarResolver.dll. + They are now extracted from the DLL when required. +* AARs / JARs are now cleaned up when switching the Android resolution + strategy. + +# Version 1.2.39 - Aug 10, 2017 +## New Features +* Android Resolver now supports resolution with Gradle. This enables support + for non-local artifacts. +## Bug Fixes +* Android Resolver's Gradle Prebuild now uses Android build tools to determine + the Android platform tools version rather than relying upon internal Unity + APIs. +* Android Resolver's Gradle Prebuild now correctly strips binaries that are + not required for the target ABI. + +# Version 1.2.38 - Aug 7, 2017 +## Bug Fixes +* Fixed an issue in VersionHandler where disabled targets are ignored if + the "Any Platform" flag is set on a plugin DLL. + +# Version 1.2.37 - Aug 3, 2017 +## New Features +* Exposed GooglePlayServices.PlayServicesResolver.Resolve() so that it's + possible for a script to be notified when AAR / Jar resolution is complete. + This makes it easier to setup a project to build from the command line. + +# Version 1.2.36 - Aug 3, 2017 +## New Features +* VersionHandler.UpdateCompleteMethods allows a user to provide a list of + methods to be called when VersionHandlerImpl has completed an update. + This makes it easier to import a plugin and wait for VersionHandler to + execute prior executing a build. + +# Version 1.2.35 - Jul 28, 2017 +## New Features +* VersionHandler will now rename Linux libraries so they can target Unity + versions that require different file naming. Libraries need to be labelled + gvh_linuxlibname-${basename} in order to be considered for renaming. + e.g gvh\_linuxlibname-MyLib will be named MyLib.so in Unity 5.5 and below and + libMyLib.so in Unity 5.6 and above. + +# Version 1.2.34 - Jul 28, 2017 +## Bug Fixes +* Made VersionHandler bootstrap module more robust when calling static + methods before the implementation DLL is loaded. + +# Version 1.2.33 - Jul 27, 2017 +## New Features +* Added a bootstrap module for VersionHandler so the implementation + of the VersionHandler module can be versioned without resulting in + a compile error when imported at different versions across multiple + plugins. + +# Version 1.2.32 - Jul 20, 2017 +## New Features +* Added support for build target selection based upon .NET framework + version in the VersionHandler. + When applying either gvh\_dotnet-3.5 or gvh\_dotnet-4.5 labels to + assets, the VersionHandler will only enable the asset for the + specified set of build targets when the matching .NET framework version + is selected in Unity 2017's project settings. This allows assets + to be provided in a plugin that need to differ based upon .NET version. + +# Version 1.2.31 - Jul 5, 2017 +## Bug Fixes +* Force expansion of AARs with native components when using Unity 2017 + with the internal build system. In contrast to Unity 5.x, Unity 2017's + internal build system does not include native libraries included in AARs. + Forcing expansion of AARs with native components generates an + Ant / Eclipse project for each AAR which is correctly included by Unity + 2017's internal build system. + +# Version 1.2.30 - Jul 5, 2017 +## Bug Fixes +* Fixed Cocoapods being installed when the build target isn't iOS. +* Added support for malformed AARs with missing classes.jar. + +# Version 1.2.29 - Jun 16, 2017 +## New Features +* Added support for the Android sdkmanager tool. + +# Version 1.2.28 - Jun 8, 2017 +## Bug Fixes +* Fixed non-shell command line execution (regression from + Cocoapod installation patch). + +# Version 1.2.27 - Jun 7, 2017 +## Bug Fixes +* Added support for stdout / stderr redirection when executing + commands in shell mode. + This fixes CocoaPod tool installation when shell mode is + enabled. +* Fixed incremental builds when additional sources are specified + in the Podfile. + +# Version 1.2.26 - Jun 7, 2017 +## Bug Fixes +* Fixed a crash when importing Version Handler into Unity 4.7.x. + +# Version 1.2.25 - Jun 7, 2017 +## Bug Fixes +* Fixed an issue in the Jar Resolver which incorrectly notified + event handlers of bundle ID changes when the currently selected + (not active) build target changed in Unity 5.6 and above. + +# Version 1.2.24 - Jun 6, 2017 +## New Features +* Added option to control file renaming in Version Handler settings. + Disabling file renaming (default option) significantly increases + the speed of file version management operations with the downside + that any files that are referenced directly by canonical filename + rather than asset ID will no longer be valid. +* Improved logging in the Version Handler. +## Bug Fixes +* Fixed an issue in the Version Handler which caused it to not + re-enable plugins when re-importing a custom package with disabled + version managed files. + +# Version 1.2.23 - May 26, 2017 +## Bug Fixes +* Fixed a bug with gradle prebuild resolver on windows. + +# Version 1.2.22 - May 19, 2017 +## Bug Fixes +* Fixed a bug in the iOS resolver with incremental builds. +* Fixed misdetection of Cocoapods support with Unity beta 5.6. + +# Version 1.2.21 - May 8, 2017 +## Bug Fixes +* Fix for https://github.com/googlesamples/unity-jar-resolver/issues/48 + Android dependency version number parsing when "-alpha" (etc.) are + included in dependency (AAR / JAR) versions. + +# Version 1.2.20 - May 8, 2017 +## Bug Fixes +* Attempted to fix + https://github.com/googlesamples/unity-jar-resolver/issues/48 + where a NullReferenceException could occur if a target file does not + have a valid version string. + +# Version 1.2.19 - May 4, 2017 +## Bug Fixes +* Fixed Jar Resolver exploding and deleting AAR files it isn't managing. + +# Version 1.2.18 - May 4, 2017 +## New Features +* Added support for preserving Unity pods such as when GVR is enabled. + +# Version 1.2.17 - Apr 20, 2017 +## Bug Fixes +* Fixed auto-resolution when an Android application ID is modified. + +# Version 1.2.16 - Apr 17, 2017 +## Bug Fixes +* Fixed Unity version number parsing on machines with a locale that uses + "," for decimal points. +* Fixed null reference exception if JDK path isn't set. + +# Version 1.2.15 - Mar 17, 2017 +## New Features +* Added warning when the Jar Resolver's background resolution is disabled. +## Bug Fixes +* Fixed support of AARs with native libraries when using Gradle. +* Fixed extra repository paths when resolving dependencies. + +# Version 1.2.14 - Mar 7, 2017 +## New Features +* Added experimental Android resolution using Gradle. + This alternative resolver supports proguard stripping with Unity's + internal build system. +* Added Android support for single ABI builds when using AARs include + native libraries. +* Disabled Android resolution on changes to all .cs and .js files. + File patterns that are monitored for auto-resolution can be added + using PlayServicesResolver.AddAutoResolutionFilePatterns(). +* Added tracking of resolved AARs and JARs so they can be cleaned up + if they're no longer referenced by a project. +* Added persistence of AAR / JAR version replacement for each Unity + session. +* Added settings dialog to the iOS resolver. +* Integrated Cocoapod tool installation in the iOS resolver. +* Added option to run pod tool via the shell. +## Bug Fixes +* Fixed build of some source Cocoapods (e.g Protobuf). +* VersionHandler no longer prompts to delete obsolete manifests. +* iOS resolver handles Cocoapod installation when using Ruby < 2.2.2. +* Added workaround for package version selection when including + Google Play Services on Android. +* Fixed support for pods that reference static libraries. +* Fixed support for resource-only pods. + +# Version 1.2.12 - Feb 14, 2017 +## Bug Fixes +* Fixed re-explosion of AARs when the bundle ID is modified. + +# Version 1.2.11 - Jan 30, 2017 +## New Features +* Added support for Android Studio builds. +* Added support for native (C/C++) shared libraries in AARs. + +# Version 1.2.10 - Jan 11, 2017 +## Bug Fixes +* Fixed SDK manager path retrieval. +* Also, report stderr when it's not possible to run the "pod" tool. +* Handle exceptions thrown by Unity.Cecil on asset rename +* Fixed IOSResolver to handle PlayerSettings.iOS.targetOSVersionString + +# Version 1.2.9 - Dec 7, 2016 +## Bug Fixes +* Improved error reporting when "pod repo update" fails. +* Added detection of xml format xcode projects generated by old Cocoapods + installations. + +# Version 1.2.8 - Dec 6, 2016 +## Bug Fixes +* Increased speed of JarResolver resolution. +* Fixed JarResolver caches getting out of sync with requested dependencies + by removing the caches. +* Fixed JarResolver explode cache always being rewritten even when no + dependencies change. + +# Version 1.2.7 - Dec 2, 2016 +## Bug Fixes +* Fixed VersionHandler build errors with Unity 5.5, due to the constantly + changing BuildTarget enum. +* Added support for Unity configured JDK Path rather than requiring + JAVA_HOME to be set in the Jar Resolver. + +# Version 1.2.6 - Nov 15, 2016 +## Bug Fixes +* Fixed IOSResolver errors when iOS support is not installed. +* Added fallback to "pod" executable search which queries the Ruby Gems + package manager for the binary install location. + +# Version 1.2.5 - Nov 3, 2016 +## Bug Fixes +* Added crude support for source only Cocoapods to the IOSResolver. + +# Version 1.2.4 - Oct 27, 2016 +## Bug Fixes +* Automated resolution of out of date pod repositories. + +# Version 1.2.3 - Oct 25, 2016 +## Bug Fixes +* Fixed exception when reporting conflicting dependencies. + +# Version 1.2.2 - Oct 17, 2016 +## Bug Fixes +* Fixed issue working with Unity 5.5 +* Fixed issue with PlayServicesResolver corrupting other iOS dependencies. +* Updated build script to use Unity distributed tools for building. + +# Version 1.2.1 - Jul 25, 2016 +## Bug Fixes +* Removed 1.2 Resolver and hardcoded whitelist of AARs to expand. +* Improved error reporting when the "jar" executable can't be found. +* Removed the need to set JAVA_HOME if "jar" is in the user's path. +* Fixed spurious copying of partially matching AARs. +* Changed resolver to only copy / expand when source AARs change. +* Auto-resolution of dependencies is now performed when the Android + build target is selected. + +## New Features +* Expand AARs that contain manifests with variable expansion like + ${applicationId}. +* Added optional logging in the JarResolverLib module. +* Integration with the Android SDK manager for dependencies that + declare required Android SDK packages. + +# Version 1.2.0 - May 11 2016 +## Bug Fixes +* Handles resolving dependencies when the artifacts are split across 2 repos. +* #4 Misdetecting version for versions like 1.2-alpha. These are now string + compared if alphanumeric +* Removed resolver creation via reflection since it did not work all the time. + Now a resolver needs to be loaded externally (which is existing behavior). + +## New Features +* Expose PlayServicesResolver properties to allow for script access. +* Explodes firebase-common and firebase-measurement aar files to support + ${applicationId} substitution. + +# Version 1.1.1 - 25 Feb 2016 +## Bug Fixes +* #1 Spaces in project path not handled when exploding Aar file. +* #2 Script compilation error: TypeLoadException. + +# Version 1.1.0 - 5 Feb 2016 +## New Features +* Adds friendly alert when JAVA_HOME is not set on Windows platforms. +* Adds flag for disabling background resolution. +* Expands play-services-measurement and replaces ${applicationId} with the + bundle Id. + + ## Bug Fixes +* Fixes infinite loop of resolution triggered by resolution. diff --git a/exploded/Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta b/exploded/Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta new file mode 100644 index 00000000..e5662a98 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: aba4ee01c6d145f7bf2d944d892f709a +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/CHANGELOG.md +- gvh +timeCreated: 1584567712 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll new file mode 100644 index 00000000..12c150e2 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta similarity index 77% rename from exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta rename to exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta index 71ee4fe2..3babd47f 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta +++ b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta @@ -1,9 +1,10 @@ fileFormatVersion: 2 -guid: 16888424fc064e5298648415a844a879 +guid: f7632a50b10045458c53a5ddf7b6d238 labels: -- gvh_version-1.2.111.0 +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/Google.VersionHandler.dll - gvh -- gvh_targets-editor +- gvhp_targets-editor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb new file mode 100644 index 00000000..7dd02af5 Binary files /dev/null and b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.111.0.dll.mdb.meta b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta similarity index 51% rename from exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.111.0.dll.mdb.meta rename to exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta index cdaa43e1..0b461abd 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.111.0.dll.mdb.meta +++ b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta @@ -1,7 +1,8 @@ fileFormatVersion: 2 -guid: 8e9ed466b9864cdb9e8be1320fa4b56b +guid: 57f5a82a79ab4b098f09326c8f3c73a6 labels: -- gvh_version-1.2.111.0 +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/Google.VersionHandler.pdb - gvh timeCreated: 1538009133 licenseType: Pro diff --git a/exploded/Assets/ExternalDependencyManager/Editor/LICENSE b/exploded/Assets/ExternalDependencyManager/Editor/LICENSE new file mode 100644 index 00000000..6258cc47 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/LICENSE @@ -0,0 +1,245 @@ +Copyright (C) 2014 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +==================================================================================================== +This package uses MiniJSON + +Copyright (c) 2013 Calvin Rien + +Based on the JSON parser by Patrick van Bergen +http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html + +Simplified it so that it doesn't throw exceptions +and can be used in Unity iPhone with maximum code stripping. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/exploded/Assets/ExternalDependencyManager/Editor/LICENSE.meta b/exploded/Assets/ExternalDependencyManager/Editor/LICENSE.meta new file mode 100644 index 00000000..30482451 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/LICENSE.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ae8b2bc8d1ac4ad48f0ab2b2e7ac75fb +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/LICENSE +- gvh +timeCreated: 1584567712 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/README.md b/exploded/Assets/ExternalDependencyManager/Editor/README.md new file mode 100644 index 00000000..a9aafe9f --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/README.md @@ -0,0 +1,903 @@ +# External Dependency Manager for Unity + +[![openupm](https://img.shields.io/npm/v/com.google.external-dependency-manager?label=openupm®istry_uri=https://package.openupm.com)](https://openupm.com/packages/com.google.external-dependency-manager/) +[![openupm](https://img.shields.io/badge/dynamic/json?color=brightgreen&label=downloads&query=%24.downloads&suffix=%2Fmonth&url=https%3A%2F%2Fpackage.openupm.com%2Fdownloads%2Fpoint%2Flast-month%2Fcom.google.external-dependency-manager)](https://openupm.com/packages/com.google.external-dependency-manager/) + +## Overview + +The External Dependency Manager for Unity (EDM4U) (formerly Play Services +Resolver/Jar Resolver) is intended to be used by any Unity package or user that +requires: + +* Android specific libraries (e.g + [AARs](https://developer.android.com/studio/projects/android-library.html)) + +* iOS [CocoaPods](https://cocoapods.org/) + +* Version management of transitive dependencies + +* Management of Package Manager (PM) Registries + +If you want to add and use iOS/Android dependencies directly in your project, +then you should to install EDM4U in your project. + +If you are a package user and the plugin you are using depends on EDM4U, *and* +the package does not include EDM4U as a package dependency already, then you +should to install EDM4U in your project. + +If you are a UPM package maintainer and your package requires EDM4U, then you +should add EDM4U as a +[package dependency](https://docs.unity3d.com/2019.3/Documentation/Manual/upm-dependencies.html) +in your package manifest (`package.json`): + +```json +{ + "dependencies": { + "com.google.external-dependency-manager": "1.2.178" + } +} +``` + +You should still install EDM4U to test out the package during development. + +If you are a legacy `.unitypackage` package maintainer and your package requires +EDM4U, please ask the user to install EDM4U separately. You should install EDM4U +to test out the package during development. + +Updated releases are available on +[GitHub](https://github.com/googlesamples/unity-jar-resolver) + +## Requirements + +The *Android Resolver* and *iOS Resolver* components of the plugin only work +with Unity version 4.6.8 or higher. + +The *Version Handler* component only works with Unity 5.x or higher as it +depends upon the `PluginImporter` UnityEditor API. + +The *Package Manager Resolver* component only works with Unity 2018.4 or above, +when [scoped registry](https://docs.unity3d.com/Manual/upm-scoped.html) support +was added to the Package Manager. + +## Getting Started + +Check out [troubleshooting](troubleshooting-faq.md) if you need help. + +### Install via OpenUPM + +EDM4U is available on +[OpenUPM](https://openupm.com/packages/com.google.external-dependency-manager/): + +```shell +openupm add com.google.external-dependency-manager +``` + +### Install via git URL +1. Open Package Manager +2. Click on the + icon on the top left corner of the "Package Manager" screen +3. Click on "Install package from git url..." +4. Paste: https://github.com/googlesamples/unity-jar-resolver.git?path=upm + +### Install via Google APIs for Unity + +EDM4U is available both in UPM and legacy `.unitypackage` formats on +[Google APIs for Unity](https://developers.google.com/unity/archive#external_dependency_manager_for_unity). + +You may install the UPM version (.tgz) as a +[local UPM package](https://docs.unity3d.com/Manual/upm-ui-local.html). + +You can also install EDM4U in your project as a `.unitypackage`. This is not +recommended due to potential conflicts. + +### Conflict Resolution + +For historical reasons, a package maintainer may choose to embed EDM4U in their +package for ease of installation. This will create a conflict when you try to +install EDM4U with the steps above, or with another package with embedded EDM4U. +If your project imported a `.unitypackage` that has a copy of EDM4U embedded in +it, you may safely delete it from your Assets folder. If your project depends on +another UPM package with EDM4U, please reach out to the package maintainer and +ask them to replace it with a dependency to this package. In the meantime, you +can workaround the issue by copying the package to your Packages folder (to +create an +[embedded package](https://docs.unity3d.com/Manual/upm-concepts.html#Embedded)) +and perform the steps yourself to avoid a dependency conflict. + +### Config file + +To start adding dependencies to your project, copy and rename the +[SampleDependencies.xml](https://github.com/googlesamples/unity-jar-resolver/blob/master/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.xml) +file into your plugin and add the dependencies your project requires. + +The XML file needs to be under an `Editor` directory and match the name +`*Dependencies.xml`. For example, `MyPlugin/Editor/MyPluginDependencies.xml`. + +## Usages + +### Android Resolver + +The Android Resolver copies specified dependencies from local or remote Maven +repositories into the Unity project when a user selects Android as the build +target in the Unity editor. + +For example, to add the Google Play Games library +(`com.google.android.gms:play-services-games` package) at version `9.8.0` to the +set of a plugin's Android dependencies: + +```xml + + + + + extra-google-m2repository + + + + +``` + +The version specification (last component) supports: + +* Specific versions e.g `9.8.0` + +* Partial matches e.g `9.8.+` would match 9.8.0, 9.8.1 etc. choosing the most + recent version + +* Latest version using `LATEST` or `+`. We do *not* recommend using this + unless you're 100% sure the library you depend upon will not break your + Unity plugin in future + +The above example specifies the dependency as a component of the Android SDK +manager such that the Android SDK manager will be executed to install the +package if it's not found. If your Android dependency is located on Maven +central it's possible to specify the package simply using the `androidPackage` +element: + +```xml + + + + + +``` + +#### Auto-resolution + +By default the Android Resolver automatically monitors the dependencies you have +specified and the `Plugins/Android` folder of your Unity project. The resolution +process runs when the specified dependencies are not present in your project. + +The *auto-resolution* process can be disabled via the `Assets > External +Dependency Manager > Android Resolver > Settings` menu. + +Manual resolution can be performed using the following menu options: + +* `Assets > External Dependency Manager > Android Resolver > Resolve` + +* `Assets > External Dependency Manager > Android Resolver > Force Resolve` + +#### Deleting libraries + +Resolved packages are tracked via asset labels by the Android Resolver. They can +easily be deleted using the `Assets > External Dependency Manager > Android +Resolver > Delete Resolved Libraries` menu item. + +#### Android Manifest Variable Processing + +Some AAR files (for example play-services-measurement) contain variables that +are processed by the Android Gradle plugin. Unfortunately, Unity does not +perform the same processing when using Unity's Internal Build System, so the +Android Resolver plugin handles known cases of this variable substitution by +exploding the AAR into a folder and replacing `${applicationId}` with the +`bundleID`. + +Disabling AAR explosion and therefore Android manifest processing can be done +via the `Assets > External Dependency Manager > Android Resolver > Settings` +menu. You may want to disable explosion of AARs if you're exporting a project to +be built with Gradle/Android Studio. + +#### ABI Stripping + +Some AAR files contain native libraries (.so files) for each ABI supported by +Android. Unfortunately, when targeting a single ABI (e.g x86), Unity does not +strip native libraries for unused ABIs. To strip unused ABIs, the Android +Resolver plugin explodes an AAR into a folder and removes unused ABIs to reduce +the built APK size. Furthermore, if native libraries are not stripped from an +APK (e.g you have a mix of Unity's x86 library and some armeabi-v7a libraries) +Android may attempt to load the wrong library for the current runtime ABI +completely breaking your plugin when targeting some architectures. + +AAR explosion and therefore ABI stripping can be disabled via the `Assets > +External Dependency Manager > Android Resolver > Settings` menu. You may want to +disable explosion of AARs if you're exporting a project to be built with +Gradle/Android Studio. + +#### Resolution Strategies + +By default the Android Resolver will use Gradle to download dependencies prior +to integrating them into a Unity project. This works with Unity's internal build +system and Gradle/Android Studio project export. + +It's possible to change the resolution strategy via the `Assets > External +Dependency Manager > Android Resolver > Settings` menu. + +##### Download Artifacts with Gradle + +Using the default resolution strategy, the Android resolver executes the +following operations: + +- Remove the result of previous Android resolutions. E.g Delete all files and + directories labeled with "gpsr" under `Plugins/Android` from the project. + +- Collect the set of Android dependencies (libraries) specified by a project's + `*Dependencies.xml` files. + +- Run `download_artifacts.gradle` with Gradle to resolve conflicts and, if + successful, download the set of resolved Android libraries (AARs, JARs). + +- Process each AAR/JAR so that it can be used with the currently selected + Unity build system (e.g Internal vs. Gradle, Export vs. No Export). This + involves patching each reference to `applicationId` in the + `AndroidManifest.xml` with the project's bundle ID. This means resolution + must be run again if the bundle ID has changed. + +- Move the processed AARs to `Plugins/Android` so they will be included when + Unity invokes the Android build. + +##### Integrate into mainTemplate.gradle + +Unity 5.6 introduced support for customizing the `build.gradle` used to build +Unity projects with Gradle. When the *Patch mainTemplate.gradle* setting is +enabled, rather than downloading artifacts before the build, Android resolution +results in the execution of the following operations: + +- Remove the result of previous Android resolutions. E.g Delete all files and + directories labeled with "gpsr" under `Plugins/Android` from the project and + remove sections delimited with `// Android Resolver * Start` and `// Android + Resolver * End` lines. + +- Collect the set of Android dependencies (libraries) specified by a project's + `*Dependencies.xml` files. + +- Rename any `.srcaar` files in the build to `.aar` and exclude them from + being included directly by Unity in the Android build as + `mainTemplate.gradle` will be patched to include them instead from their + local maven repositories. + +- Inject the required Gradle repositories into `mainTemplate.gradle` at the + line matching the pattern `.*apply plugin: + 'com\.android\.(application|library)'.*` or the section starting at the line + `// Android Resolver Repos Start`. If you want to control the injection + point in the file, the section delimited by the lines `// Android Resolver + Repos Start` and `// Android Resolver Repos End` should be placed in the + global scope before the `dependencies` section. + +- Inject the required Android dependencies (libraries) into + `mainTemplate.gradle` at the line matching the pattern `***DEPS***` or the + section starting at the line `// Android Resolver Dependencies Start`. If + you want to control the injection point in the file, the section delimited + by the lines `// Android Resolver Dependencies Start` and `// Android + Resolver Dependencies End` should be placed in the `dependencies` section. + +- Inject the packaging options logic, which excludes architecture specific + libraries based upon the selected build target, into `mainTemplate.gradle` + at the line matching the pattern `android +{` or the section starting at the + line `// Android Resolver Exclusions Start`. If you want to control the + injection point in the file, the section delimited by the lines `// Android + Resolver Exclusions Start` and `// Android Resolver Exclusions End` should + be placed in the global scope before the `android` section. + +#### Dependency Tracking + +The Android Resolver creates the +`ProjectSettings/AndroidResolverDependencies.xml` to quickly determine the set +of resolved dependencies in a project. This is used by the auto-resolution +process to only run the expensive resolution process when necessary. + +#### Displaying Dependencies + +It's possible to display the set of dependencies the Android Resolver would +download and process in your project via the `Assets > External Dependency +Manager > Android Resolver > Display Libraries` menu item. + +### iOS Resolver + +The iOS resolver component of this plugin manages +[CocoaPods](https://cocoapods.org/). A CocoaPods `Podfile` is generated and the +`pod` tool is executed as a post build process step to add dependencies to the +Xcode project exported by Unity. + +Dependencies for iOS are added by referring to CocoaPods. + +For example, to add the AdMob pod, version 7.0 or greater with bitcode enabled: + +```xml + + + + + +``` + +#### Integration Strategies + +The `CocoaPods` are either: + +* Downloaded and injected into the Xcode project file directly, rather than + creating a separate xcworkspace. We call this `Xcode project` integration. + +* If the Unity version supports opening a xcworkspace file, the `pod` tool is + used as intended to generate a xcworkspace which references the CocoaPods. + We call this `Xcode workspace` integration. + +The resolution strategy can be changed via the `Assets > External Dependency +Manager > iOS Resolver > Settings` menu. + +##### Appending text to generated Podfile + +In order to modify the generated Podfile you can create a script like this: + +```csharp +using System.IO; + +using UnityEditor; +using UnityEditor.Callbacks; +using UnityEngine; + +public class PostProcessIOS : MonoBehaviour +{ + // Must be between 40 and 50 to ensure that it's not overriden by Podfile generation (40) and + // that it's added before "pod install" (50). + [PostProcessBuildAttribute(45)] + private static void PostProcessBuild_iOS(BuildTarget target, string buildPath) + { + if (target == BuildTarget.iOS) + { + using (StreamWriter sw = File.AppendText(buildPath + "/Podfile")) + { + // E.g. add an app extension + sw.WriteLine("\ntarget 'NSExtension' do\n pod 'Firebase/Messaging', '6.6.0'\nend"); + } + } + } +} +``` + +### Package Manager Resolver + +Adding registries to the +[Package Manager](https://docs.unity3d.com/Manual/Packages.html) (PM) is a +manual process. The Package Manager Resolver (PMR) component of this plugin +makes it easy for plugin maintainers to distribute new PM registry servers and +easy for plugin users to manage PM registry servers. + +#### Adding Registries + +For example, to add a registry for plugins in the scope `com.coolstuff`: + +```xml + + + + com.coolstuff + + + +``` + +When PMR is loaded it will prompt the developer to add the registry to their +project if it isn't already present in the `Packages/manifest.json` file. + +For more information, see Unity's documentation on +[scoped package registries](https://docs.unity3d.com/Manual/upm-scoped.html). + +#### Managing Registries + +It's possible to add and remove registries that are specified via PMR XML +configuration files via the following menu options: + +* `Assets > External Dependency Manager > Package Manager Resolver > Add + Registries` will prompt the user with a window which allows them to add + registries discovered in the project to the Package Manager. + +* `Assets > External Dependency Manager > Package Manager Resolver > Remove + Registries` will prompt the user with a window which allows them to remove + registries discovered in the project from the Package Manager. + +* `Assets > External Dependency Manager > Package Manager Resolver > Modify + Registries` will prompt the user with a window which allows them to add or + remove registries discovered in the project. + +#### Migration + +PMR can migrate Version Handler packages installed in the `Assets` folder to PM +packages. This requires the plugins to implement the following: + +* `.unitypackage` must include a Version Handler manifests that describes the + components of the plugin. If the plugin has no dependencies the manifest + would just include the files in the plugin. + +* The PM package JSON provided by the registry must include a keyword (in the + `versions.VERSION.keyword` list) that maps the PM package to a Version + Handler package using the format `vh-name:VERSION_HANDLER_MANIFEST_NAME` + where `VERSION_HANDLER_MANIFEST_NAME` is the name of the manifest defined in + the `.unitypackage`. For more information see the description of the + `gvhp_manifestname` asset label in the [Version Handler](#version-handler) + section. + +When using the `Assets > External Dependency Manager > Package Manager +Resolver > Migrate Packages` menu option, PMR then will: + +* List all Version Handler manager packages in the project. + +* Search all available packages in the PM registries and fetch keywords + associated with each package parsing the Version Handler manifest names for + each package. + +* Map each installed Version Handler package to a PM package. + +* Prompt the user to migrate the discovered packages. + +* Perform package migration for all selected packages if the user clicks the + `Apply` button. + +#### Configuration + +PMR can be configured via the `Assets > External Dependency Manager > Package +Manager Resolver > Settings` menu option: + +* `Add package registries` when enabled, when the plugin loads or registry + configuration files change, this will prompt the user to add registries that + are not present in the Package Manager. + +* `Prompt to add package registries` will cause a developer to be prompted + with a window that will ask for confirmation before adding registries. When + this is disabled registries are added silently to the project. + +* `Prompt to migrate packages` will cause a developer to be prompted with a + window that will ask for confirmation before migrating packages installed in + the `Assets` directory to PM packages. + +* `Enable Analytics Reporting` when enabled, reports the use of the plugin to + the developers so they can make imrpovements. + +* `Verbose logging` when enabled prints debug information to the console which + can be useful when filing bug reports. + +### Version Handler + +The Version Handler component of this plugin manages: + +* Shared Unity plugin dependencies. + +* Upgrading Unity plugins by cleaning up old files from previous versions. + +* Uninstallation of plugins that are distributed with manifest files. + +* Restoration of plugin assets to their original install locations if assets + are tagged with the `exportpath` label. + +Since the Version Handler needs to modify Unity asset metadata (`.meta` files), +to enable/disable components, rename and delete asset files it does not work +with Package Manager installed packages. It's still possible to include EDM4U in +Package Manager packages, the Version Handler component simply won't do anything +to PM plugins in this case. + +#### Using Version Handler Managed Plugins + +If a plugin is imported at multiple different versions into a project, if the +Version Handler is enabled, it will automatically check all managed assets to +determine the set of assets that are out of date and assets that should be +removed. To disable automatic checking managed assets disable the `Enable +version management` option in the `Assets > External Dependency Manager > +Version Handler > Settings` menu. + +If version management is disabled, it's possible to check managed assets +manually using the `Assets > External Dependency Manager > Version Handler > +Update` menu option. + +##### Listing Managed Plugins + +Plugins managed by the Version Handler, those that ship with manifest files, can +displayed using the `Assets > External Dependency Manager > Version Handler > +Display Managed Packages` menu option. The list of plugins are written to the +console window along with the set of files used by each plugin. + +##### Uninstalling Managed Plugins + +Plugins managed by the Version Handler, those that ship with manifest files, can +be removed using the `Assets > External Dependency Manager > Version Handler > +Uninstall Managed Packages` menu option. This operation will display a window +that allows a developer to select a set of plugins to remove which will remove +all files owned by each plugin excluding those that are in use by other +installed plugins. + +Files managed by the Version Handler, those labeled with the `gvh` asset label, +can be checked to see whether anything needs to be upgraded, disabled or removed +using the `Assets > External Dependency Manager > Version Handler > Update` menu +option. + +##### Restore Install Paths + +Some developers move assets around in their project which can make it harder for +plugin maintainers to debug issues if this breaks Unity's +[special folders](https://docs.unity3d.com/Manual/SpecialFolders.html) rules. If +assets are labeled with their original install/export path (see +`gvhp_exportpath` below), Version Handler can restore assets to their original +locations when using the `Assets > External Dependency Manager > Version +Handler > Move Files To Install Locations` menu option. + +##### Settings + +Some behavior of the Version Handler can be configured via the `Assets > +External Dependency Manager > Version Handler > Settings` menu option. + +* `Enable version management` controls whether the plugin should automatically + check asset versions and apply changes. If this is disabled the process + should be run manually when installing or upgrading managed plugins using + `Assets > External Dependency Manager > Version Handler > Update`. + +* `Rename to canonical filenames` is a legacy option that will rename files to + remove version numbers and other labels from filenames. + +* `Prompt for obsolete file deletion` enables the display of a window when + obsolete files are deleted allowing the developer to select which files to + delete and those to keep. + +* `Allow disabling files via renaming` controls whether obsolete or disabled + files should be disabled by renaming them to `myfilename_DISABLED`. Renaming + to disable files is required in some scenarios where Unity doesn't support + removing files from the build via the PluginImporter. + +* `Enable Analytics Reporting` enables/disables usage reporting to plugin + developers to improve the product. + +* `Verbose logging` enables *very* noisy log output that is useful for + debugging while filing a bug report or building a new managed plugin. + +* `Use project settings` saves settings for the plugin in the project rather + than system-wide. + +#### Redistributing a Managed Plugin + +The Version Handler employs a couple of methods for managing version selection, +upgrade and removal of plugins. + +* Each plugin can ship with a manifest file that lists the files it includes. + This makes it possible for Version Handler to calculate the difference in + assets between the most recent release of a plugin and the previous release + installed in a project. If a files are removed the Version Handler will + prompt the user to clean up obsolete files. + +* Plugins can ship using assets with unique names, unique GUIDs and version + number labels. Version numbers can be attached to assets using labels or + added to the filename (e.g `myfile.txt` would be `myfile_version-x.y.z.txt). + This allows the Version Handler to determine which set of files are the same + file at different versions, select the most recent version and prompt the + developer to clean up old versions. + +Unity plugins can be managed by the Version Handler using the following steps: + +1. Add the `gvh` asset label to each asset (file) you want Version Handler to + manage. + +1. Add the `gvh_version-VERSION` label to each asset where `VERSION` is the + version of the plugin you're releasing (e.g 1.2.3). + +1. Add the `gvhp_exportpath-PATH` label to each asset where `PATH` is the + export path of the file when the `.unitypackage` is created. This is used to + track files if they're moved around in a project by developers. + +1. Optional: Add `gvh_targets-editor` label to each editor DLL in your plugin + and disable `editor` as a target platform for the DLL. The Version Handler + will enable the most recent version of this DLL when the plugin is imported. + +1. Optional: If your plugin is included in other Unity plugins, you should add + the version number to each filename and change the GUID of each asset. This + allows multiple versions of your plugin to be imported into a Unity project, + with the Version Handler component activating only the most recent version. + +1. Create a manifest text file named `MY_UNIQUE_PLUGIN_NAME_VERSION.txt` that + lists all the files in your plugin relative to the project root. Then add + the `gvh_manifest` label to the asset to indicate this file is a plugin + manifest. + +1. Optional: Add a `gvhp_manifestname-NAME` label to your manifest file to + provide a human readable name for your package. If this isn't provided the + name of the manifest file will be used as the package name. NAME can match + the pattern `[0-9]+[a-zA-Z -]` where a leading integer will set the priority + of the name where `0` is the highest priority and preferably used as the + display name. The lowest value (i.e highest priority name) will be used as + the display name and all other specified names will be aliases of the + display name. Aliases can refer to previous names of the package allowing + renaming across published versions. + +1. Redistribute EDM4U Unity plugin with your plugin. See the + [Plugin Redistribution](#plugin-redistribution) section for details. + +If you follow these steps: + +* When users import a newer version of your plugin, files referenced by the + older version's manifest are cleaned up. + +* The latest version of the plugin will be selected when users import multiple + packages that include your plugin, assuming the steps in + [Plugin Redistribution](#plugin-redistribution) are followed. + +## Background + +Many Unity plugins have dependencies upon Android specific libraries, iOS +CocoaPods, and sometimes have transitive dependencies upon other Unity plugins. +This causes the following problems: + +* Integrating platform specific (e.g Android and iOS) libraries within a Unity + project can be complex and a burden on a Unity plugin maintainer. +* The process of resolving conflicting dependencies on platform specific + libraries is pushed to the developer attempting to use a Unity plugin. The + developer trying to use your plugin is very likely to give up when faced + with Android or iOS specific build errors. +* The process of resolving conflicting Unity plugins (due to shared Unity + plugin components) is pushed to the developer attempting to use your Unity + plugin. In an effort to resolve conflicts, the developer will very likely + attempt to resolve problems by deleting random files in your plugin, report + bugs when that doesn't work and finally give up. + +EDM4U provides solutions for each of these problems. + +### Android Dependency Management + +The *Android Resolver* component of this plugin will download and integrate +Android library dependencies and handle any conflicts between plugins that share +the same dependencies. + +Without the Android Resolver, typically Unity plugins bundle their AAR and JAR +dependencies, e.g. a Unity plugin `SomePlugin` that requires the Google Play +Games Android library would redistribute the library and its transitive +dependencies in the folder `SomePlugin/Android/`. When a user imports +`SomeOtherPlugin` that includes the same libraries (potentially at different +versions) in `SomeOtherPlugin/Android/`, the developer using `SomePlugin` and +`SomeOtherPlugin` will see an error when building for Android that can be hard +to interpret. + +Using the Android Resolver to manage Android library dependencies: + +* Solves Android library conflicts between plugins. +* Handles all of the various processing steps required to use Android + libraries (AARs, JARs) in Unity 4.x and above projects. Almost all versions + of Unity have - at best - partial support for AARs. +* (Experimental) Supports minification of included Java components without + exporting a project. + +### iOS Dependency Management + +The *iOS Resolver* component of this plugin integrates with +[CocoaPods](https://cocoapods.org/) to download and integrate iOS libraries and +frameworks into the Xcode project Unity generates when building for iOS. Using +CocoaPods allows multiple plugins to utilize shared components without forcing +developers to fix either duplicate or incompatible versions of libraries +included through multiple Unity plugins in their project. + +### Package Manager Registry Setup + +The [Package Manager](https://docs.unity3d.com/Manual/Packages.html) (PM) makes +use of [NPM](https://www.npmjs.com/) registry servers for package hosting and +provides ways to discover, install, upgrade and uninstall packages. This makes +it easier for developers to manage plugins within their projects. + +However, installing additional package registries requires a few manual steps +that can potentially be error prone. The *Package Manager Resolver* component of +this plugin integrates with [PM](https://docs.unity3d.com/Manual/Packages.html) +to provide a way to auto-install PM package registries when a `.unitypackage` is +installed which allows plugin maintainers to ship a `.unitypackage` that can +provide access to their own PM registry server to make it easier for developers +to manage their plugins. + +### Unity Plugin Version Management + +Finally, the *Version Handler* component of this plugin simplifies the process +of managing transitive dependencies of Unity plugins and each plugin's upgrade +process. + +For example, without the Version Handler plugin, if: + +* Unity plugin `SomePlugin` includes `EDM4U` plugin at version 1.1. +* Unity plugin `SomeOtherPlugin` includes `EDM4U` plugin at version 1.2. + +The version of `EDM4U` included in the developer's project depends upon the +order the developer imports `SomePlugin` or `SomeOtherPlugin`. + +This results in: + +* `EDM4U` at version 1.2, if `SomePlugin` is imported then `SomeOtherPlugin` + is imported. +* `EDM4U` at version 1.1, if `SomeOtherPlugin` is imported then `SomePlugin` + is imported. + +The Version Handler solves the problem of managing transitive dependencies by: + +* Specifying a set of packaging requirements that enable a plugin at different + versions to be imported into a Unity project. +* Providing activation logic that selects the latest version of a plugin + within a project. + +When using the Version Handler to manage `EDM4U` included in `SomePlugin` and +`SomeOtherPlugin`, from the prior example, version 1.2 will always be the +version activated in a developer's Unity project. + +Plugin creators are encouraged to adopt this library to ease integration for +their customers. For more information about integrating EDM4U into your own +plugin, see the [Plugin Redistribution](#plugin-redistribution) section of this +document. + +## Analytics + +The External Dependency Manager for Unity plugin by default logs usage to Google +Analytics. The purpose of the logging is to quantitatively measure the usage of +functionality, to gather reports on integration failures and to inform future +improvements to the developer experience of the External Dependency Manager +plugin. Note that the analytics collected are limited to the scope of the EDM4U +plugin’s usage. + +For details of what is logged, please refer to the usage of +`EditorMeasurement.Report()` in the source code. + +## Plugin Redistribution + +If you are a package maintainer and your package depends on EDM4U, it is highly +recommended to use the UPM format and add EDM4U as a dependency. If you must +include it in your `.unitypackage`, redistributing `EDM4U` inside your own +plugin might ease the integration process for your users. + +If you wish to redistribute `EDM4U` inside your plugin, you **must** follow +these steps when importing the `external-dependency-manager-*.unitypackage`, and +when exporting your own plugin package: + +1. Import the `external-dependency-manager-*.unitypackage` into your plugin + project by + [running Unity from the command line](https://docs.unity3d.com/Manual/CommandLineArguments.html), + ensuring that you add the `-gvh_disable` option. +1. Export your plugin by + [running Unity from the command line](https://docs.unity3d.com/Manual/CommandLineArguments.html), + ensuring that you: + - Include the contents of the `Assets/PlayServicesResolver` and + `Assets/ExternalDependencyManager` directory. + - Add the `-gvh_disable` option. + +You **must** specify the `-gvh_disable` option in order for the Version Handler +to work correctly! + +For example, the following command will import the +`external-dependency-manager-1.2.46.0.unitypackage` into the project +`MyPluginProject` and export the entire Assets folder to +`MyPlugin.unitypackage`: + +```shell +Unity -gvh_disable \ + -batchmode \ + -importPackage external-dependency-manager-1.2.46.0.unitypackage \ + -projectPath MyPluginProject \ + -exportPackage Assets MyPlugin.unitypackage \ + -quit +``` + +### Background + +The *Version Handler* component relies upon deferring the load of editor DLLs so +that it can run first and determine the latest version of a plugin component to +activate. The build of `EDM4U` plugin has Unity asset metadata that is +configured so that the editor components are not initially enabled when it's +imported into a Unity project. To maintain this configuration when importing the +`external-dependency-manager.unitypackage` into a Unity plugin project, you +*must* specify the command line option `-gvh_disable` which will prevent the +Version Handler component from running and changing the Unity asset metadata. + +## Building from Source + +To build this plugin from source you need the following tools installed: * Unity +2021 and below (with iOS and Android modules installed) * Java 11 + +You can build the plugin by running the following from your shell (Linux / OSX): + +```shell +./gradlew build + +``` + +or Windows: + +```shell +./gradlew.bat build +``` + +If Java 11 is not your default Java command, add +`-Dorg.gradle.java.home=` to the command above. + +## Testing + +You can run the tests by running the following from your shell (Linux / OSX): + +```shell +./gradlew test +``` + +or Windows: + +```shell +./gradlew.bat test +``` + +The following properties can be set to narrow down the tests to run or change +the test run behavior. + +* `INTERACTIVE_MODE_TESTS_ENABLED` - Default to `1`. Set to `1` to enable + interactive mode tests, which requires GPU on the machine. Otherwise, only + run tests in the batch mode. +* `INCLUDE_TEST_TYPES` - Default to empty string, which means to include every + type of the test. To narrow down the types of test to run, set this + properties with a list of case-insensitive type strings separated by comma. + For instance, `-PINCLUDE_TEST_TYPES="Python,NUnit"` means to include only + Python tests and NUnit tests. See `TestTypeEnum` in `build.gradle` for + available options. +* `EXCLUDE_TEST_TYPES` - Default to empty string, which means to exclude none. + To add types of tests to exclude, set this properties with a list of + case-insensitive type strings separated by comma. For instance, + `-PEXCLUDE_TEST_TYPES="Python,NUnit"` means to exclude Python tests and + NUnit tests. See `TestTypeEnum` in `build.gradle` for available options. +* `INCLUDE_TEST_MODULES` - Default to empty string, which means to include the + tests for every modules. To narrow down modules to test, set this properties + with a list of case-insensitive module strings separated by comma. For + instance, `-PINCLUDE_TEST_MODULES="Tool,AndroidResolver"` means to run tests + for tools and Android Resolver only. See `TestModuleEnum` in `build.gradle` + for available options. +* `EXCLUDE_TEST_MODULES` - Default to empty string, which means to exclude + none. To add modules to exclude, set this properties with a list of + case-insensitive module strings separated by comma. For instance, + `-PEXCLUDE_TEST_MODULES="Tool,AndroidResolver"` means to run tests for any + modules other than tools and Android Resolver. See `TestModuleEnum` in + `build.gradle` for available options. +* `EXCLUDE_TESTS` - Default to empty string, which means to exclude none. To + add tests to exclude, set this properties with a list of case-insensitive + test names separated by comma. For instance, + `-PEXCLUDE_TESTS="testGenGuids,testDownloadArtifacts"` means to run tests + except the tests with name of `testGenGuids` and `testDownloadArtifacts`. +* `CONTINUE_ON_FAIL_FOR_TESTS_ENABLED` - Default to `1`. Set to `1` to + continue running the next test when the current one fails. Otherwise, the + build script stops whenever any test fails. + +For instance, by running the following command, it only runs the Unity +integration tests that does not requires GPU, but exclude tests for Android +Resolver module and iOS Resolver module. + +```shell +./gradlew test \ + -PINTERACTIVE_MODE_TESTS_ENABLED=0 \ + -PINCLUDE_TEST_TYPES="Integration" \ + -PEXCLUDE_TEST_MODULES="AndroidResolver,iOSResolver" +``` + +## Releasing + +Each time a new build of this plugin is checked into the source tree you need to +do the following: + +* Bump the plugin version variable `pluginVersion` in `build.gradle` +* Update `CHANGELOG.md` with the new version number and changes included in + the release. +* Build the release using `./gradlew release` which performs the following: + * Updates `external-dependency-manager-*.unitypackage` + * Copies the unpacked plugin to the `exploded` directory. + * Updates template metadata files in the `plugin` directory. The GUIDs of + all asset metadata is modified due to the version number change. Each + file within the plugin is versioned to allow multiple versions of the + plugin to be imported into a Unity project which allows the most recent + version to be activated by the Version Handler component. +* Create release commit using `./gradlew gitCreateReleaseCommit` which + performs `git commit -a -m "description from CHANGELOG.md"` +* Once the release commit is merge, tag the release using `./gradlew + gitTagRelease` which performs the following: + * `git tag -a pluginVersion -m "version RELEASE"` to tag the release. +* Update tags on remote branch using `git push --tag REMOTE HEAD:master` diff --git a/exploded/Assets/ExternalDependencyManager/Editor/README.md.meta b/exploded/Assets/ExternalDependencyManager/Editor/README.md.meta new file mode 100644 index 00000000..6bcc2245 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/README.md.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 77919e84cef8419ab4b725fc16e83d52 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/README.md +- gvh +timeCreated: 1584567712 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt b/exploded/Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt new file mode 100644 index 00000000..81c97ed6 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt @@ -0,0 +1,13 @@ +Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll +Assets/ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb +Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll +Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb +Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll +Assets/ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb +Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll +Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb +Assets/ExternalDependencyManager/Editor/CHANGELOG.md +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb +Assets/ExternalDependencyManager/Editor/LICENSE +Assets/ExternalDependencyManager/Editor/README.md diff --git a/exploded/Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt.meta b/exploded/Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt.meta new file mode 100644 index 00000000..a36e708a --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt.meta @@ -0,0 +1,15 @@ +fileFormatVersion: 2 +guid: c9a3138961c74d99b7046b783112fceb +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.186_manifest.txt +- gvh +- gvh_manifest +- gvhp_manifestname-0External Dependency Manager +- gvhp_manifestname-play-services-resolver +timeCreated: 1474401009 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.111.0.dll b/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.111.0.dll deleted file mode 100644 index 56478c6a..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.111.0.dll and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.111.0.dll.mdb b/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.111.0.dll.mdb deleted file mode 100644 index 787d8841..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.111.0.dll.mdb and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.111.0.dll b/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.111.0.dll deleted file mode 100644 index 86119a62..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.111.0.dll and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.111.0.dll.mdb b/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.111.0.dll.mdb deleted file mode 100644 index ccdd3550..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.111.0.dll.mdb and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll b/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll deleted file mode 100644 index f3b87932..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb b/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb deleted file mode 100644 index 638a69e4..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.111.0.dll b/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.111.0.dll deleted file mode 100644 index 173353a2..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.111.0.dll and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.111.0.dll.mdb b/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.111.0.dll.mdb deleted file mode 100644 index 0a6e857d..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.111.0.dll.mdb and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.111.0.txt b/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.111.0.txt deleted file mode 100644 index bcefdde6..00000000 --- a/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.111.0.txt +++ /dev/null @@ -1,8 +0,0 @@ -Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.111.0.dll -Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.111.0.dll.mdb -Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.111.0.dll -Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.111.0.dll.mdb -Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll -Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb -Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.111.0.dll -Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.111.0.dll.mdb diff --git a/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt b/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt new file mode 100644 index 00000000..a0268fcc --- /dev/null +++ b/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt @@ -0,0 +1,2 @@ +Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll +Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb diff --git a/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.111.0.txt.meta b/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta similarity index 72% rename from exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.111.0.txt.meta rename to exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta index 84a7af46..af4c6c44 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.111.0.txt.meta +++ b/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 -guid: 95220fbd3fba4bc59b870a022c8c4736 +guid: ba6f911c6f9d4d9ea269756e9dafb641 labels: -- gvh_version-1.2.111.0 +- gvh_version-1.2.137.0 - gvh - gvh_manifest timeCreated: 1474401009 diff --git a/export_unity_package_config.json b/export_unity_package_config.json new file mode 100644 index 00000000..78b83ecb --- /dev/null +++ b/export_unity_package_config.json @@ -0,0 +1,96 @@ +{ + "packages": [ + { + "name": "external-dependency-manager.unitypackage", + "imports": [ + { + "importer": "PluginImporter", + "platforms": ["Editor"], + "paths": [ + "ExternalDependencyManager/Editor/Google.VersionHandler.*" + ] + }, + { + "importer": "PluginImporter", + "platforms": [], + "labels": ["gvhp_targets-editor"], + "paths": [ + "ExternalDependencyManager/Editor/*/Google.IOSResolver.*", + "ExternalDependencyManager/Editor/*/Google.JarResolver.*", + "ExternalDependencyManager/Editor/*/Google.VersionHandlerImpl.*", + "ExternalDependencyManager/Editor/*/Google.PackageManagerResolver.*" + ], + "override_metadata_upm": { + "PluginImporter": { + "platformData": [ { + "first" : { + "Editor": "Editor" + }, + "second": { + "enabled": 1 + } + } + ] + } + } + }, + { + "sections": ["documentation"], + "importer": "DefaultImporter", + "paths": [ + "ExternalDependencyManager/Editor/README.md", + "ExternalDependencyManager/Editor/CHANGELOG.md", + "ExternalDependencyManager/Editor/LICENSE" + ] + }, + { + "importer": "DefaultImporter", + "paths": [ + "ExternalDependencyManager/Editor/external-dependency-manager*_manifest.txt" + ] + }, + { + "sections": ["unitypackage"], + "importer": "DefaultImporter", + "paths": [ + "PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt" + ] + } + ], + "manifest_path": "ExternalDependencyManager/Editor", + + "readme": "ExternalDependencyManager/Editor/README.md", + "license": "ExternalDependencyManager/Editor/LICENSE", + "changelog": "ExternalDependencyManager/Editor/CHANGELOG.md", + "documentation": "ExternalDependencyManager/Editor/README.md", + + "common_manifest" : { + "name": "com.google.external-dependency-manager", + "display_name": "External Dependency Manager for Unity", + "description": [ + "External Dependency Manager for Unity (EDM4U) can be used by any ", + "Unity plugin that requires Android specific libraries (e.g. AARs), ", + "iOS CocoaPods, version management of transitive dependencies, ", + "and/or management of Unity Package Manager registries." + ], + "keywords": [ + "Google", "Android", "Gradle", "Cocoapods", "Dependency", + "Unity Package Manager", "Unity", + "vh-name:play-services-resolver", + "vh-name:unity-jar-resolver" + ], + "author": { + "name" : "Google LLC", + "url": "https://github.com/googlesamples/unity-jar-resolver" + } + }, + + "export_upm" : 1, + "upm_package_config" : { + "manifest" : { + "unity": "2019.1" + } + } + } + ] +} diff --git a/export_unity_package_guids.json b/export_unity_package_guids.json new file mode 100644 index 00000000..59c045f4 --- /dev/null +++ b/export_unity_package_guids.json @@ -0,0 +1,73 @@ +{ + "1.2.137": { + "com.google.external-dependency-manager/CHANGELOG.md": "dd6a29a412594aadb37d9698db325eca", + "com.google.external-dependency-manager/ExternalDependencyManager": "46f5870ddbde4a6091f50656dcd5573e", + "com.google.external-dependency-manager/ExternalDependencyManager/Editor": "1f23cd25474841f6ad7555705b4807e9", + "com.google.external-dependency-manager/LICENSE.md": "f61a1c8e753b496bb696e77d7eedfb95", + "com.google.external-dependency-manager/README.md": "cbbebcaa6ecb4b9582dce440a386de75", + "com.google.external-dependency-manager/package.json": "9bed450d5c03481d87e61b61431cf00a" + }, + "1.2.166": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.166": "9dfec94683154487ab08de0c50179674" + }, + "1.2.167": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.167": "75c93e39c49442c9a6c4aea09cc5b982" + }, + "1.2.168": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.168": "224d14327d134b0ea5ba8ce2deaa6768" + }, + "1.2.169": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.169": "02ea964fdc804b098a302e49a57d8cd3" + }, + "1.2.170": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.170": "cd46d4272b824509b5b5544ec9b34870" + }, + "1.2.171": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.171": "8281940745814cc5b50954561f0e2582" + }, + "1.2.172": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.172": "3f9604d812054a74a5726176ad683fb3" + }, + "1.2.173": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.173": "fdeeb5a9eafe4d2bbd8a94a41eb5302e" + }, + "1.2.174": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.174": "2a82085e2b06441b96c360b7fe97f98c" + }, + "1.2.175": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.175": "ba0c697ad191428fac6b53f4ca8f8d4a" + }, + "1.2.176": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.176": "864a72f7c7cf4153a532cc1277dbfbdb" + }, + "1.2.177": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.177": "a768f4126d3b41ac900586e5a3b2d4a1" + }, + "1.2.178": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.178": "3d894de101f34a4b9a7f42e13c7aa3a3" + }, + "1.2.179": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.179": "e4b2f028f2e949cfac3ba5bbb128d139" + }, + "1.2.180": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.180": "e8f303eb74064b86b1308e81afd52ce1" + }, + "1.2.181": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.181": "d27fe564f10147d1bfded8244b25b494" + }, + "1.2.182": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.182": "60e9ce1be3474de5bdded37d1462e09b" + }, + "1.2.183": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.183": "5c127f68af44472d8099a80364a25718" + }, + "1.2.184": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.184": "297013161bc34ce4ac7b8ffba2384862" + }, + "1.2.185": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.185": "eb66eed64a99451d81abd63ff417f837" + }, + "1.2.186": { + "com.google.external-dependency-manager/ExternalDependencyManager/Editor/1.2.186": "8a6a9bd2649d4370b43be75e1689748c" + } +} diff --git a/external-dependency-manager-1.2.186.unitypackage b/external-dependency-manager-1.2.186.unitypackage new file mode 100644 index 00000000..37e58cd2 Binary files /dev/null and b/external-dependency-manager-1.2.186.unitypackage differ diff --git a/external-dependency-manager-latest.unitypackage b/external-dependency-manager-latest.unitypackage new file mode 100644 index 00000000..37e58cd2 Binary files /dev/null and b/external-dependency-manager-latest.unitypackage differ diff --git a/gha/build_setup/action.yml b/gha/build_setup/action.yml new file mode 100644 index 00000000..aaacd456 --- /dev/null +++ b/gha/build_setup/action.yml @@ -0,0 +1,72 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Reusable cross-platform workflow to setup for the build environment. +# * Install Python and required Python packages +# * Install Unity, platform build support, and register license if +# username, password and serial id are provided +name: 'Build Setup' + +inputs: + unity_version: + required: true + platform: + description: 'Platform to install Unity on (Windows,macOS,Linux)' + type: choice + options: + - Windows + - macOS + - Linux + required: true + unity_username: + required: false + unity_password: + required: false + unity_serial_id: + required: false + python_version: + required: true + +runs: + using: 'composite' + steps: + # Download GHA tools and requirements from Firebase Unity SDK repo + - uses: actions/checkout@v3 + with: + repository: firebase/firebase-unity-sdk + path: external/firebase-unity-sdk + sparse-checkout: | + scripts/gha/requirements.txt + sparse-checkout-cone-mode: false + + - name: Setup python + uses: actions/setup-python@v4 + with: + python-version: ${{ inputs.python_version }} + + - name: Install python deps + shell: bash + run: | + pip install -r ./external/firebase-unity-sdk/scripts/gha/requirements.txt + + - name: Install Unity and get a license + uses: firebase/firebase-unity-sdk/gha/unity@main + with: + version: ${{ inputs.unity_version }} + # iOS build support is always required to build EDM4U + platforms: "${{ inputs.platform }},iOS,Android" + username: ${{ inputs.unity_username }} + password: ${{ inputs.unity_password }} + serial_ids: ${{ inputs.unity_serial_id }} + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 60d7aa5c..b129e8fb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip diff --git a/maven-indexes/dl.google.com/dl/android/maven2/index.json b/maven-indexes/dl.google.com/dl/android/maven2/index.json new file mode 100644 index 00000000..7d50bfd7 --- /dev/null +++ b/maven-indexes/dl.google.com/dl/android/maven2/index.json @@ -0,0 +1 @@ +{"update_time":1562097600000,"groups":[{"group":"com.android.support.constraint","update_time":-1,"packages":[{"package":"constraint-layout-solver","versions":[{"version":"1.0.2","sha1":"b9cd8fc6bd15cb915735d34535db30ece0c44603"},{"version":"1.1.0-beta1","sha1":"4205dd8c33ada1468ab377afff0be95304ef72a"},{"version":"1.1.0-beta2","sha1":"f9d6991c400841332d4f878848e3e72c5cc57ac0"},{"version":"1.1.0-beta3","sha1":"1a822bfce8ec55cadddedfe602f8c523250d7bc6"},{"version":"1.1.0-beta4","sha1":"a2b04ed50b6ea66c1a4922a1fffe9d7f0df0a749"},{"version":"1.1.0-beta5","sha1":"9bc9e32d75211b9c4cea65617c9d6b217c1a264a"},{"version":"1.1.0-beta6","sha1":"16c2d7faeb42b8f8afde9555e6afabcba5ac631c"},{"version":"1.1.0","sha1":"931532e953a477f876f2de18c2e7f16eee01078f"},{"version":"1.1.1","sha1":"cece6996f6bc526ed0d8453a43765bcf8f2b6018"},{"version":"1.1.2","sha1":"bfc967828daffc35ba01c9ee204d98b664930a0f"},{"version":"1.1.3","sha1":"bde0667d7414c16ed62d3cfe993cff7f9d732373"},{"version":"2.0.0-alpha1","sha1":"c52a709906b6e12648439935269c89fff744dc27"},{"version":"2.0.0-alpha2","sha1":"5bc811fe2082f97d2e6b68e847f31844e4fb10e2"},{"version":"2.0.0-alpha3","sha1":"1b6c08d1b6e51ad651c77396e607b2a93c00a72c"},{"version":"2.0.0-alpha4","sha1":"d94cb4fcddd950da101ea70667bee82a0113fa05"},{"version":"2.0.0-alpha5","sha1":"dbe6a23bfc590499348744f21afd335d92fb3be"},{"version":"2.0.0-beta1","sha1":"a18feafbff8daa896c5136e20a37a2cc8292b258"},{"version":"2.0.0-beta2","sha1":"b072fe65082cad1b608e1f0f5c42a08260c7a4ca"}]},{"package":"constraint-layout","versions":[{"version":"1.0.2","sha1":"4f47352b754e5b418b481dce696d7b92bf870f64"},{"version":"1.1.0-beta1","sha1":"d5d20537f0e1921e75f5d57cb9d967d13c8c15c9"},{"version":"1.1.0-beta2","sha1":"b3a48fa5af8db3a4888430bbb22bf01d32c012a7"},{"version":"1.1.0-beta3","sha1":"dcbf1a25f495a40ea2b413beae6c6815e04b2cff"},{"version":"1.1.0-beta4","sha1":"df287537c77e522089967aea8fd0adb574258bf5"},{"version":"1.1.0-beta5","sha1":"b054b630faa4a743fdced317e5fb986bfa33f264"},{"version":"1.1.0-beta6","sha1":"e3eac586f60be9fa162d7c33a933784c9ab29e1e"},{"version":"1.1.0","sha1":"902b820320ab6759a43a20a34b4630fd5a2a68e2"},{"version":"1.1.1","sha1":"c6c1712f3132a4ef7105d13827b5f93db1280309"},{"version":"1.1.2","sha1":"9d3ebf4014f91080ca936647e4a21649c3175718"},{"version":"1.1.3","sha1":"2f88a748b5e299029c65a126bd718b2c2ac1714"},{"version":"2.0.0-alpha1","sha1":"abe1736f1dc685d15d0ca1ec563f184279c0492f"},{"version":"2.0.0-alpha2","sha1":"74c404fe4af843464ae51dcebff8731029afc80d"},{"version":"2.0.0-alpha3","sha1":"6e2f65e6a43cd0808004556c21b4fd033c11f1a9"},{"version":"2.0.0-alpha4","sha1":"14ac778fc21782a86816361957d3096a0bea18a1"},{"version":"2.0.0-alpha5","sha1":"2d9f8493b8629a05092e57c7776f74a1d1ac9835"},{"version":"2.0.0-beta1","sha1":"1972819c1629f2faecf7d02a0ee2e8b8a9e67a3"},{"version":"2.0.0-beta2","sha1":"385ff0d2250025ca73b3e68cdfebd3d0c3989b86"}]}]},{"group":"com.android.databinding","update_time":-1,"packages":[{"package":"library","versions":[{"version":"1.0-rc0","sha1":"44aaf2becff3080f293cb6f4602e38475967a854"},{"version":"1.0-rc1","sha1":"480013f76091cc7c972fe36d3569a23ae3d51d7f"},{"version":"1.0-rc2","sha1":"7f64376f157a6c9c460037bf7ff8d2b131683a1f"},{"version":"1.0-rc3","sha1":"33a2cacd38ea6e7ea750a50b1be3e5ecddc07fd2"},{"version":"1.0-rc5","sha1":"aba6c5f9ad349a476c59826b53c1a2deb1af3add"},{"version":"1.1","sha1":"6d305d8c838bbb1e16584a3053097699b5353d11"},{"version":"1.2","sha1":"9960b502a0cf85cb5cd9bcab512d52688cb12015"},{"version":"1.2.1","sha1":"3910ec7cd7e76e365bd5fa9a7b1da858390d3500"},{"version":"1.3","sha1":"84b3c9bd91a15a7e930110b879af341549dec0fe"},{"version":"1.3.1","sha1":"1701fbb3e89088376a2d3f3e6c40a57bcac88186"},{"version":"1.3.3","sha1":"86f63214d9bd2b2c1591914e914ce225871299"},{"version":"3.1.0-alpha05","sha1":"31159f8e48c9cd6a5b613133f3af68867b9a9141"},{"version":"3.1.0-alpha06","sha1":"63ca0414853aee125e70f09d83468dd8809c1224"},{"version":"3.1.0-alpha07","sha1":"63ca0414853aee125e70f09d83468dd8809c1224"},{"version":"3.1.0-alpha08","sha1":"e32f594de4168ce06e80d467a948fa370c3db155"},{"version":"3.1.0-alpha09","sha1":"e32f594de4168ce06e80d467a948fa370c3db155"},{"version":"3.1.0-beta1","sha1":"e32f594de4168ce06e80d467a948fa370c3db155"},{"version":"3.1.0-beta2","sha1":"e32f594de4168ce06e80d467a948fa370c3db155"},{"version":"3.1.0-beta3","sha1":"41da9ffe77a15fe5856a5faca90b3640edc233fb"},{"version":"3.1.0-beta4","sha1":"ea87000a2672dd48671e3fa143ba38a0049499c6"},{"version":"3.1.0-rc01","sha1":"41da9ffe77a15fe5856a5faca90b3640edc233fb"},{"version":"3.1.0-rc02","sha1":"41da9ffe77a15fe5856a5faca90b3640edc233fb"},{"version":"3.1.0-rc03","sha1":"41da9ffe77a15fe5856a5faca90b3640edc233fb"},{"version":"3.1.0","sha1":"41da9ffe77a15fe5856a5faca90b3640edc233fb"},{"version":"3.1.1","sha1":"41da9ffe77a15fe5856a5faca90b3640edc233fb"},{"version":"3.1.2","sha1":"41da9ffe77a15fe5856a5faca90b3640edc233fb"},{"version":"3.1.3","sha1":"41da9ffe77a15fe5856a5faca90b3640edc233fb"},{"version":"3.1.4","sha1":"41da9ffe77a15fe5856a5faca90b3640edc233fb"},{"version":"3.2.0-alpha01","sha1":"e32f594de4168ce06e80d467a948fa370c3db155"},{"version":"3.2.0-alpha02","sha1":"e32f594de4168ce06e80d467a948fa370c3db155"},{"version":"3.2.0-alpha03","sha1":"41da9ffe77a15fe5856a5faca90b3640edc233fb"},{"version":"3.2.0-alpha04","sha1":"41da9ffe77a15fe5856a5faca90b3640edc233fb"},{"version":"3.2.0-alpha05","sha1":"cf3360154b6ff96fc6bac2da9faa579071227b15"},{"version":"3.2.0-alpha06","sha1":"cf3360154b6ff96fc6bac2da9faa579071227b15"},{"version":"3.2.0-alpha07","sha1":"cf3360154b6ff96fc6bac2da9faa579071227b15"},{"version":"3.2.0-alpha08","sha1":"aa1f7f3a489b0c07ac056ea9e7eaa1b70755561f"},{"version":"3.2.0-alpha09","sha1":"cf3360154b6ff96fc6bac2da9faa579071227b15"},{"version":"3.2.0-alpha10","sha1":"cf3360154b6ff96fc6bac2da9faa579071227b15"},{"version":"3.2.0-alpha11","sha1":"cf3360154b6ff96fc6bac2da9faa579071227b15"},{"version":"3.2.0-alpha12","sha1":"cf3360154b6ff96fc6bac2da9faa579071227b15"},{"version":"3.2.0-alpha13","sha1":"cf3360154b6ff96fc6bac2da9faa579071227b15"},{"version":"3.2.0-alpha14","sha1":"cf3360154b6ff96fc6bac2da9faa579071227b15"},{"version":"3.2.0-alpha15","sha1":"cf3360154b6ff96fc6bac2da9faa579071227b15"},{"version":"3.2.0-alpha16","sha1":"cf3360154b6ff96fc6bac2da9faa579071227b15"},{"version":"3.2.0-alpha17","sha1":"cf3360154b6ff96fc6bac2da9faa579071227b15"},{"version":"3.2.0-alpha18","sha1":"80905eb6c3b6a77a4b293e1d5a649a222ec335f8"},{"version":"3.2.0-beta01","sha1":"eaf71b653348cb2c75db71989ff0d7cb1f113aea"},{"version":"3.2.0-beta02","sha1":"2830206b3219cbdbfe3eb65a2554d8506571f545"},{"version":"3.2.0-beta03","sha1":"456bef99b17a8a52d3d626a9b1225d43f9cc81b7"},{"version":"3.2.0-beta04","sha1":"b75a63f840c4f2bff7c87d4303eade20b3f1c42c"},{"version":"3.2.0-beta05","sha1":"2441e455296c9bd5e1f286efdc31d42dd7082a88"},{"version":"3.2.0-rc01","sha1":"d917dace54d266b3a02a646b4a7b8fc4783bb2dd"},{"version":"3.2.0-rc02","sha1":"3815b6bfd6dc192514789834707b5a1d9cf7d7f"},{"version":"3.2.0-rc03","sha1":"d26c4fb671bc08c7a5bcabff650da08c9dc83c05"},{"version":"3.2.0","sha1":"88ecaf8d4b22173f138c58d04a24cdc1ca238af8"},{"version":"3.2.1","sha1":"bd4fcd755f79a96fa2c8a7e9b618d1d7a99823ef"},{"version":"3.3.0-alpha01","sha1":"be45465b50f81133161cfdea565c17bb13ed802e"},{"version":"3.3.0-alpha02","sha1":"633bba49beef4e2c5786baa06eb055f9cc83c06d"},{"version":"3.3.0-alpha03","sha1":"4cd4b600a9c02ba1a914e0e1e26dd1d9d7f5910a"},{"version":"3.3.0-alpha04","sha1":"f65cf54ed545530dac41a7fb4e1ade5651f239cc"},{"version":"3.3.0-alpha05","sha1":"36dd68e83fb579f72ebd0877bce4c78eda8fed2d"},{"version":"3.3.0-alpha06","sha1":"b7216edec446717d383217a2e1dbd46e0c0f6767"},{"version":"3.3.0-alpha07","sha1":"1eb53c5075a55cc4d0bb7239f2e53220b89b5dc6"},{"version":"3.3.0-alpha08","sha1":"f29d083b85db07ebf64d8e4609a883d5158fe6e5"},{"version":"3.3.0-alpha09","sha1":"2489667504ef16d984da577eca01b6e12059fc20"},{"version":"3.3.0-alpha10","sha1":"108bbd4e87712891770fc9936ec71193f3a99bab"},{"version":"3.3.0-alpha11","sha1":"c0c0a4e51110b02a115c0d4f273721650cd786df"},{"version":"3.3.0-alpha12","sha1":"d1674e6f34e1a42ce59bb88a9b3815d809ae713a"},{"version":"3.3.0-alpha13","sha1":"a6beb02f44483b6e44381327ee10abc3794d2a23"},{"version":"3.3.0-beta01","sha1":"54d2e22ad9bf4a56f31ff2db2f3ecb7fddec8c90"},{"version":"3.3.0-beta02","sha1":"55eb139a7127f4204a74e62b16660e4f0c8d07b4"},{"version":"3.3.0-beta03","sha1":"da3a3dc873808424de68fdeedb74ebec67c74a45"},{"version":"3.3.0-beta04","sha1":"46635c5e9afbce565301b7bb65f384a449279215"},{"version":"3.3.0-rc01","sha1":"b80e04983faf7ffe50f63965d0b6b9005e3faf2f"},{"version":"3.3.0-rc02","sha1":"4ad02af853da427cf2b99385106bb4fc6458b161"},{"version":"3.3.0-rc03","sha1":"bae5d0e6563dcee7bcffa4ce5e4a46f636253584"},{"version":"3.3.0","sha1":"ec0f066abd52b347f1f220070bae89894a4a02d4"},{"version":"3.3.1","sha1":"131382ab78b277f7822defa56d4a5fbd73021b31"},{"version":"3.3.2","sha1":"4b924cca78f212da88a942020533c949d1294a80"},{"version":"3.4.0-alpha01","sha1":"e71d15bc66e7c28987b7684a917edae92b95775"},{"version":"3.4.0-alpha02","sha1":"be483ad15d6a724ede465d9aecd368e90e8b6f38"},{"version":"3.4.0-alpha03","sha1":"be055bb99ca3cb223c08b7064aa8082c6bd612c5"},{"version":"3.4.0-alpha04","sha1":"815eed1cae7ee81875d4df072bf5eb8a0bb9bdfa"},{"version":"3.4.0-alpha05","sha1":"17dbf6b6694453d3513c1403ef50dc8d6d324877"},{"version":"3.4.0-alpha06","sha1":"c9a5168d9addb0288d604787015cb708c307f8b2"},{"version":"3.4.0-alpha07","sha1":"890c92eceda931ae20bbb91d76e2985659e53f9c"},{"version":"3.4.0-alpha08","sha1":"ea490929a8796001075f5e5fd611b7a053c56a54"},{"version":"3.4.0-alpha09","sha1":"2e1a9fa8d1310a74e4595b734932013cc0255849"},{"version":"3.4.0-alpha10","sha1":"232a64597b8a9d633af6cfd1091619275c05f7f0"},{"version":"3.4.0-beta01","sha1":"f3f8b7a60ecc36a530e6de951691e26f21c395c8"},{"version":"3.4.0-beta02","sha1":"6f7e312bddaf31bb5eb618869ced86ea2879a42c"},{"version":"3.4.0-beta03","sha1":"a1637241ae55b362a0e0db7605426b9e55d36af"},{"version":"3.4.0-beta04","sha1":"377d27b0c890fb1e5e237e2044ae72ec80099e0f"},{"version":"3.4.0-beta05","sha1":"2901dfa9ad1837a6c99e9767e4159f3698e328f5"},{"version":"3.4.0-rc01","sha1":"9b6f1e7d87a93254b37061e85607601fc9c33ee0"},{"version":"3.4.0-rc02","sha1":"527d38318108f07f5a865876bd26cf4378b3bf45"},{"version":"3.4.0-rc03","sha1":"dc7f1da127a1e9fc6143280a378744dc80b30429"},{"version":"3.4.0","sha1":"1706ee89f47332af8c948fa895d562b2bef3d404"},{"version":"3.4.1","sha1":"69d1130dcd6987b3be5a22c20cedde39b9540df8"},{"version":"3.4.2","sha1":"9d984b02462375e2f1cba9ac8a190a09eb13e17f"},{"version":"3.5.0-alpha01","sha1":"84417ab9142eefa0681239aee5ad8318e771da41"},{"version":"3.5.0-alpha02","sha1":"9d97b36905fbefb785a3968c32f1d8de14a80e3d"},{"version":"3.5.0-alpha03","sha1":"32ad3c67a665019774cb4e2da2dcfb0f7e9b4000"},{"version":"3.5.0-alpha04","sha1":"a304efd73bad937fdbd0e15c55576b27646c637c"},{"version":"3.5.0-alpha05","sha1":"e81b23e6edbc72056e09b1efcf91b5bd7abfb667"},{"version":"3.5.0-alpha06","sha1":"9cf8de8ce75486f5a05848a51f1d7324534c402b"},{"version":"3.5.0-alpha07","sha1":"21c936452397ab6052393516fd5dfabc94fee43e"},{"version":"3.5.0-alpha08","sha1":"8df32bad9b61fdee09353289d06fe5559ff23d94"},{"version":"3.5.0-alpha09","sha1":"39a427a321642bc19ea8750485076b21eb1d27fe"},{"version":"3.5.0-alpha10","sha1":"965892c40f78de4860921218c425ce85fb60475d"},{"version":"3.5.0-alpha11","sha1":"4693a50ed9195dff369d9c146e1ef747a83e06fd"},{"version":"3.5.0-alpha12","sha1":"d710d4a6f55a52dfa391d7ccae0cf06a8e46b84"},{"version":"3.5.0-alpha13","sha1":"628d7c35e75944527146980b65d9cb4637de75f3"},{"version":"3.5.0-beta01","sha1":"d91bc2a9fc9beadca28c800aa9f9990c4aac9ad1"},{"version":"3.5.0-beta02","sha1":"d2b03bd141409d684603f9a3162559fe4e6ceaa5"},{"version":"3.5.0-beta03","sha1":"c82ca6074b5bf92e86e80011bdf4675f7894e86e"},{"version":"3.5.0-beta04","sha1":"c747c22ec21dc6fa92f753146338037cf176298"},{"version":"3.5.0-beta05","sha1":"ed9a76784da3a15a66cf6123434633db707723e3"},{"version":"3.6.0-alpha01","sha1":"542c9dc1cc7d3a5a4741d8c8ad963a0c14d0b7e6"},{"version":"3.6.0-alpha02","sha1":"e7f37519b274f4c9ba970aa3e6ff9080e0d7f142"},{"version":"3.6.0-alpha03","sha1":"4b21216d9ccbf0cfb31b2481a104e4ce3b71a1d6"},{"version":"3.6.0-alpha04","sha1":"418911cb90ed28b9f7aae9bb97906db69f0434eb"}]},{"package":"adapters","versions":[{"version":"1.0-rc0","sha1":"43cd8b4485a9770ddc4bbf0eb28ba3a188d0880b"},{"version":"1.0-rc1","sha1":"8a82ed058e9e709740dd89c88e1be77196a71eee"},{"version":"1.0-rc2","sha1":"4f1d025ce8bcfadc9ed308439da56e2382c91360"},{"version":"1.0-rc3","sha1":"cda5ac229c41ab2000a394eaa9e4dbf33adc8658"},{"version":"1.0-rc5","sha1":"1ac2f4916e93f32049bbc564217f51fabed2955"},{"version":"1.1","sha1":"110c163a806a7317374e98d9a8ab460e91c0d3d3"},{"version":"1.2","sha1":"b8b18e4f9e2d27d5e3fe4b45e7df3459e4170b7f"},{"version":"1.2.1","sha1":"2bc5679c19f0ad5a1f838c80e64561904e0bdb16"},{"version":"1.3","sha1":"1b53359786a15266ee0fadaa289ea287c171c115"},{"version":"1.3.1","sha1":"8b777c53d7198488edd7e44ad2f94adf22e2cac6"},{"version":"1.3.3","sha1":"bbc15f876979784340aa9d7b53a0777f1d7f6c40"},{"version":"3.1.0-alpha05","sha1":"30347066fc00f900478ac126ac0c7b44710b1bd8"},{"version":"3.1.0-alpha06","sha1":"6e8b432e256c5d7e77f3c421dac1ce136b511888"},{"version":"3.1.0-alpha07","sha1":"3a9ca03b5d0aa16dd2e7c143342e51c460f5fb68"},{"version":"3.1.0-alpha08","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.1.0-alpha09","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.1.0-beta1","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.1.0-beta2","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.1.0-beta3","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.1.0-beta4","sha1":"ff7ba58ba9bc6b0b19bb5d47dfc04ba3700e67d8"},{"version":"3.1.0-rc01","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.1.0-rc02","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.1.0-rc03","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.1.0","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.1.1","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.1.2","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.1.3","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.1.4","sha1":"e89f203428168ebf22dc53713f0245e7bdd00b65"},{"version":"3.2.0-alpha01","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.2.0-alpha02","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.2.0-alpha03","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.2.0-alpha04","sha1":"d43ea207240bfc63aab704274a553a933e0c4c8"},{"version":"3.2.0-alpha05","sha1":"18c3d59e2113bdfdcc509549ca5ddd37529664e7"},{"version":"3.2.0-alpha06","sha1":"18c3d59e2113bdfdcc509549ca5ddd37529664e7"},{"version":"3.2.0-alpha07","sha1":"18c3d59e2113bdfdcc509549ca5ddd37529664e7"},{"version":"3.2.0-alpha08","sha1":"7de29b5d93b2c038985f81c2443e965ff2cdd649"},{"version":"3.2.0-alpha09","sha1":"18c3d59e2113bdfdcc509549ca5ddd37529664e7"},{"version":"3.2.0-alpha10","sha1":"18c3d59e2113bdfdcc509549ca5ddd37529664e7"},{"version":"3.2.0-alpha11","sha1":"18c3d59e2113bdfdcc509549ca5ddd37529664e7"},{"version":"3.2.0-alpha12","sha1":"18c3d59e2113bdfdcc509549ca5ddd37529664e7"},{"version":"3.2.0-alpha13","sha1":"18c3d59e2113bdfdcc509549ca5ddd37529664e7"},{"version":"3.2.0-alpha14","sha1":"18c3d59e2113bdfdcc509549ca5ddd37529664e7"},{"version":"3.2.0-alpha15","sha1":"18c3d59e2113bdfdcc509549ca5ddd37529664e7"},{"version":"3.2.0-alpha16","sha1":"18c3d59e2113bdfdcc509549ca5ddd37529664e7"},{"version":"3.2.0-alpha17","sha1":"f2c6bbed0c5cdd2999ddaefd81f5dad289e9cb7c"},{"version":"3.2.0-alpha18","sha1":"cf287c1181bc87b46468d20ae20517f838e2489d"},{"version":"3.2.0-beta01","sha1":"45e217b74c80f43cc4426bcb635ef9faa632d1a2"},{"version":"3.2.0-beta02","sha1":"e3a5b83b114cdc91f7705cc9e0addfaf98e8b0d3"},{"version":"3.2.0-beta03","sha1":"3d1e08c6e72c898a82ee18612c0c2f66c6fdf104"},{"version":"3.2.0-beta04","sha1":"61d2eeba4c726d4331892e8d213709e3a003f811"},{"version":"3.2.0-beta05","sha1":"fbb4d514ea823581a0e4063b32a756c817367a44"},{"version":"3.2.0-rc01","sha1":"49a7555de65b407f11fdf3c87a8f9c7bd7420bf8"},{"version":"3.2.0-rc02","sha1":"6dd4fdb9881dca8c208e51c23b15b79e20c2a57f"},{"version":"3.2.0-rc03","sha1":"dedc458b7cadc74851fbcd8f4feb4f664770db2e"},{"version":"3.2.0","sha1":"16e9555157c3589b2baf183b185878603bdafa4c"},{"version":"3.2.1","sha1":"ae0b85890c327757d0b20f8159c249a2ae162c55"},{"version":"3.3.0-alpha01","sha1":"d6fb1354ebe86c630ef9f1d7e3c4f08bdc79007"},{"version":"3.3.0-alpha02","sha1":"f1937d927e69007bfab39c7fff5d497e7cdcf045"},{"version":"3.3.0-alpha03","sha1":"1615eb5fc3adf886f3f7bd6e0e6035e9b507f022"},{"version":"3.3.0-alpha04","sha1":"4805e540fad48f5799b31cb9597448bb8b11e8f1"},{"version":"3.3.0-alpha05","sha1":"b23486cfaa798e1ad9ae1a1cbe05691629c705ca"},{"version":"3.3.0-alpha06","sha1":"fb545bc5d3a240d673df2e854f6e9839a3d79390"},{"version":"3.3.0-alpha07","sha1":"607fa6322cdb8aba9d7c95afc4a5dd6a710cfbb9"},{"version":"3.3.0-alpha08","sha1":"d698e432975684114f511cf080ecdd7f7076df29"},{"version":"3.3.0-alpha09","sha1":"7c55928e57f337b160a6042df2c2669dacee16ef"},{"version":"3.3.0-alpha10","sha1":"25dcfb8ffe984d1037f6802fd07cfedff53e8de9"},{"version":"3.3.0-alpha11","sha1":"d4cb6b2a8c648e79d44da914ffd9284ff0f58d38"},{"version":"3.3.0-alpha12","sha1":"e3ccca20b4de0d1394f3ba53e32860e599b614cf"},{"version":"3.3.0-alpha13","sha1":"c2e3291d6012a93c62ab941ac7139cad7c731519"},{"version":"3.3.0-beta01","sha1":"cb2b13c907b56ef75d0e1d64504b4df0c091a70"},{"version":"3.3.0-beta02","sha1":"54c54c0787a99a0494e98db098b7fb69d341cb98"},{"version":"3.3.0-beta03","sha1":"58d71fc777bb20a6a73c6b50b11106b2e8b82da4"},{"version":"3.3.0-beta04","sha1":"5aea61b6833310f4fff3b64fb49cbe858bb7a9f3"},{"version":"3.3.0-rc01","sha1":"b305a847f403d56eba0707d2e056e70527754627"},{"version":"3.3.0-rc02","sha1":"116943f5fbb9e0749e12b0fe6d94ba0361389771"},{"version":"3.3.0-rc03","sha1":"b6217e281523487e9fc9d92cf21faf3926872f36"},{"version":"3.3.0","sha1":"9ae89acecd8c65effc9791f8cf68108ac2f1c3b2"},{"version":"3.3.1","sha1":"5bf700dec6f14682580ed6d8d1dc12401d9f62f6"},{"version":"3.3.2","sha1":"298936714a5c1d1159ca33db26831c1ba8fce220"},{"version":"3.4.0-alpha01","sha1":"384a0bc09d01455f1732e5aa060fc91848f9065d"},{"version":"3.4.0-alpha02","sha1":"c04183785ce0874fa45afd6cca88d317a0853d7c"},{"version":"3.4.0-alpha03","sha1":"cee0c7d59a3bd1910c8673b8974d7d7b2f6d8677"},{"version":"3.4.0-alpha04","sha1":"18938b5298863717fb3bf0fbeec13af604bb7e03"},{"version":"3.4.0-alpha05","sha1":"8b7b4d227f487598a767da4b02ffbba3a48d6c1c"},{"version":"3.4.0-alpha06","sha1":"b76308c839dc45827a0bf2aa4ee0de6add425f4d"},{"version":"3.4.0-alpha07","sha1":"dd445c26bc6e2e1b6847255b69e791418d74b9a1"},{"version":"3.4.0-alpha08","sha1":"62154fec3adbdb70cd07464d7282eeb41856f323"},{"version":"3.4.0-alpha09","sha1":"923910382c889809c7439552017485e8e5695b93"},{"version":"3.4.0-alpha10","sha1":"4aa628f36c126382a27d44fb991ca814f89adad8"},{"version":"3.4.0-beta01","sha1":"4ff54e141b1a8b18e07eafb4f913abbace5ef4c0"},{"version":"3.4.0-beta02","sha1":"68f179b7060bc5c7e6f279b89ba05f06fbf405b5"},{"version":"3.4.0-beta03","sha1":"f7ea700147cc31edbfe595c7bb5e2d83b2dba55"},{"version":"3.4.0-beta04","sha1":"3659d936167c88afcf3e68d40acf9ac5014f3bb9"},{"version":"3.4.0-beta05","sha1":"264ecfae0a3285adb82d9349e3ebb0bda55d9cd8"},{"version":"3.4.0-rc01","sha1":"4af41323d6213ad85307d14fad8f1df5be1dacfb"},{"version":"3.4.0-rc02","sha1":"c44ec5977c0f250dc263e7a878409af725d570b8"},{"version":"3.4.0-rc03","sha1":"c65d3871498a094cfc00a012a8911a5938993225"},{"version":"3.4.0","sha1":"5cc4e26374d2a0abf281230f41b1348ccaa26bd2"},{"version":"3.4.1","sha1":"4ab6d0aae9381d5c1df0ff246b7e3a2825ead1ff"},{"version":"3.4.2","sha1":"b989b5e5d5d2f4794fd12da570877c8f9449d393"},{"version":"3.5.0-alpha01","sha1":"fe82c03f3482c853bcc01016e0654520a2bb4eb8"},{"version":"3.5.0-alpha02","sha1":"88661e9678e0de863e9dc5cadc42167bf14345e3"},{"version":"3.5.0-alpha03","sha1":"1161f81abaa58b8ff33308c1512207deb0be4c5d"},{"version":"3.5.0-alpha04","sha1":"8c94e388034eba8452ef50f461dd63e99b0e8f38"},{"version":"3.5.0-alpha05","sha1":"99fff60416af9df5496a2519d95f6eb54d22de06"},{"version":"3.5.0-alpha06","sha1":"2ebe1e5467b060c6cfddedaeb3b588e50aaeb372"},{"version":"3.5.0-alpha07","sha1":"fa21d6345263caccea6a30b7d09bfe7debb0449c"},{"version":"3.5.0-alpha08","sha1":"d74e1a991e2f40342115b87b46eba74aa3e217f8"},{"version":"3.5.0-alpha09","sha1":"4cc5dd0adcb54f7e1b5ea0d5cac9180b8316bfce"},{"version":"3.5.0-alpha10","sha1":"bbaa8cbf05cd68ad05a2828403826ba90c0c9c46"},{"version":"3.5.0-alpha11","sha1":"1baf5e6de257e5e9560893688de3649afe496c64"},{"version":"3.5.0-alpha12","sha1":"39e0f277e05294255bb1a7e13be2d8d4a6e99b71"},{"version":"3.5.0-alpha13","sha1":"9dd0daf0f74c61fd0757506ae86b5d9238d55873"},{"version":"3.5.0-beta01","sha1":"8f7968fd86edf545367864f1125c148d02e81143"},{"version":"3.5.0-beta02","sha1":"b7b92a81fb189c18edb3e0153aca6457cc3791b9"},{"version":"3.5.0-beta03","sha1":"3dfa27b40e44cbd0afd247822c4d2f8f2d26dec6"},{"version":"3.5.0-beta04","sha1":"6cec3e8feed5cb23659b7d022d6bf2812704b1bc"},{"version":"3.5.0-beta05","sha1":"db2b4ddb6f063d6873db13632d2ac839dde46083"},{"version":"3.6.0-alpha01","sha1":"19f46c231ab706fb78765e97faa54de98c1581"},{"version":"3.6.0-alpha02","sha1":"ef29f652683609786439f243f894225c5871308c"},{"version":"3.6.0-alpha03","sha1":"7e597f95f3fcee2ac3a1393e47f2e497533d0b19"},{"version":"3.6.0-alpha04","sha1":"fa82d9bd105ea53fb9ca082e3483b9d005d52568"}]},{"package":"compiler","versions":[{"version":"3.0.0-alpha1","sha1":"d46c799ca1926f539e7ba1f0f119524fda8d91e8"},{"version":"3.0.0-alpha2","sha1":"dac4475ff3c0adc3f6151ea0aebc36a2b51997f6"},{"version":"3.0.0-alpha3","sha1":"869db9a1d3844ea823cd5f48250f323136ccaa19"},{"version":"3.0.0-alpha4","sha1":"d0141c3da766a17284bc8a50582f59977de31ccf"},{"version":"3.0.0-alpha5","sha1":"2a5980e21974aadfeb4288900a6c77ef81925f20"},{"version":"3.0.0-alpha6","sha1":"5dd7964fdf15037c81317e2ccf45242439f18e50"},{"version":"3.0.0-alpha7","sha1":"34ee08b2deb8ddfea75945cff7fac949faa4f5c8"},{"version":"3.0.0-alpha8","sha1":"c8de9b800e94359ec763e2d73c093c58313b153d"},{"version":"3.0.0-alpha9","sha1":"6d6594af2be167552a2bf79d2d775f400770ee22"},{"version":"3.0.0-beta1","sha1":"182f9ab97d1dd6cced2da934803f5bca2244339a"},{"version":"3.0.0-beta2","sha1":"e04cc1b61d68c733248b3202b970e38061494092"},{"version":"3.0.0-beta3","sha1":"855612c1c703207abf6ac2822015c9a3aaeca187"},{"version":"3.0.0-beta4","sha1":"1aa31b7f5b6ed50024b4b54057290e9f20203fba"},{"version":"3.0.0-beta5","sha1":"32173dda67e1401e6584476c854c0002cc23e3d3"},{"version":"3.0.0-beta6","sha1":"277487193ec133e2aca7b728d6fe36c33133f4f7"},{"version":"3.0.0-beta7","sha1":"6e7dc9ac95cf23e1f1199757354897b32d110163"},{"version":"3.0.0-rc1","sha1":"f97e4bd2b3a3696d658d86d757856318f5d52b65"},{"version":"3.0.0-rc2","sha1":"59741bee4a3dd0066a81bfd8bbd8cfde67e97cfc"},{"version":"3.0.0","sha1":"bbba39120a0efd5ea97368a07127f2dde159ee64"},{"version":"3.0.1","sha1":"87c2ff9b5e52d8a4df81cd378bd8a43be2dc9cdd"},{"version":"3.1.0-alpha01","sha1":"3332c06d4be8cac43b065db42dc82d9c59ab99f8"},{"version":"3.1.0-alpha02","sha1":"29b854531569967603288f19549090fd5a1a4e22"},{"version":"3.1.0-alpha03","sha1":"394a47d2dcc328d8c935b0f45c2bed5b35e4c7b"},{"version":"3.1.0-alpha04","sha1":"748c82eb49657021a1689da9306ee1886e18d450"},{"version":"3.1.0-alpha05","sha1":"ce91fbe519afefd6363db346444a56d62b63e162"},{"version":"3.1.0-alpha06","sha1":"c4a751e7d163ee5a41cee76796492b0790e87754"},{"version":"3.1.0-alpha07","sha1":"5c1db8dc666869d934a92eadea0e0eb70010579b"},{"version":"3.1.0-alpha08","sha1":"8c8dfc9f678bd1ffb9568619452be5e4c542306c"},{"version":"3.1.0-alpha09","sha1":"b77d1a4e706a50b0fbe9a730a9f8c9383cea70be"},{"version":"3.1.0-beta1","sha1":"9bf7e3a98ecf3d4f0d3cc3e2bc45b895d8a32a3b"},{"version":"3.1.0-beta2","sha1":"7beb59650f87db01ab0ad99da0603c86f6cc7bb4"},{"version":"3.1.0-beta3","sha1":"e448501ad2c8aae3da2045925675cdfd805b6b1a"},{"version":"3.1.0-beta4","sha1":"667d5e38bfdf61c573778035fd3530b303b7e12d"},{"version":"3.1.0-rc01","sha1":"c6d05213d41b0a4c0d92a999ffbf26bb0c28284e"},{"version":"3.1.0-rc02","sha1":"a67ebc9ef782ad80df4a53cae330fbbaba6f7373"},{"version":"3.1.0-rc03","sha1":"ac17bce969006283a7fd35906c498f7aec9204c8"},{"version":"3.1.0","sha1":"d5d9698b1fd8d1065a6b0ee717712ea594c4b3bb"},{"version":"3.1.1","sha1":"5669a3a13b9ab0f207da2cd98344f47c780c9afb"},{"version":"3.1.2","sha1":"76ce7b33d28beeba96072ee4a858fee846a0bcf6"},{"version":"3.1.3","sha1":"57b68f62b18a0b246d5e09eb8552534e945a3d30"},{"version":"3.1.4","sha1":"18b1273e7727beae96629393470fc866e70b937c"},{"version":"3.2.0-alpha01","sha1":"424472ac922cd8b48ff68325af403bebf5d84f31"},{"version":"3.2.0-alpha02","sha1":"5fc3af1639fb716025c8970bdb2a89ee628599eb"},{"version":"3.2.0-alpha03","sha1":"72114fc8ec859e7ef6d655a2e7fc869180084818"},{"version":"3.2.0-alpha04","sha1":"65bd53f1a7c8728012c41396b989c793384da732"},{"version":"3.2.0-alpha05","sha1":"8d2b9294cb258bbeaa3a8d5ae82fa5cb6e07364e"},{"version":"3.2.0-alpha06","sha1":"d2cb44d56efa10fee1b58b81ec590c9a95e414e8"},{"version":"3.2.0-alpha07","sha1":"f548877857dafe629df5940b5a5277a6f1ff1af"},{"version":"3.2.0-alpha08","sha1":"4eac2d27ac4e19306e3b0d73d3fe7907e88687e0"},{"version":"3.2.0-alpha09","sha1":"f605ff97d3e5692d2fbda1bb9bd895824ae3b8e7"},{"version":"3.2.0-alpha10","sha1":"5fe08e5b5593bd2f59dd719971a3335ac948be26"}]},{"package":"compilerCommon","versions":[{"version":"3.0.0-alpha1","sha1":"6ea80f6d3dd68dce1e8aa8aa5ee4128086f8dfc5"},{"version":"3.0.0-alpha2","sha1":"b38bc568058b3e937c4f72ab550cf8e7982ab047"},{"version":"3.0.0-alpha3","sha1":"ded13e370deba086ccd3135e1323b6d554868544"},{"version":"3.0.0-alpha4","sha1":"2eff9ac752bf07d927b82ec67c830f037c05a2af"},{"version":"3.0.0-alpha5","sha1":"a3adb4937800875aac26ee8c44f77fde1940b251"},{"version":"3.0.0-alpha6","sha1":"2dfd556204bb8c961da63fe7d4aaf714a5a22b7"},{"version":"3.0.0-alpha7","sha1":"8f97342ebf3f8501276dccf4ff6e0be862b660fd"},{"version":"3.0.0-alpha8","sha1":"8605528853c19d83179fb2ae9db0ed24066b9aa"},{"version":"3.0.0-alpha9","sha1":"ba2d5a77ff8c850eae830393e9392a4783ca3baf"},{"version":"3.0.0-beta1","sha1":"b3bc364cf06e4a011178bf39d8b356cf1329757d"},{"version":"3.0.0-beta2","sha1":"7566489f8b7f7e4a2f14094862bf517486259a16"},{"version":"3.0.0-beta3","sha1":"fb779260579a0ae28a8f7aed3c05962bed00d393"},{"version":"3.0.0-beta4","sha1":"40b92d56941cb3a2b3cb59e85b4af1be812e1d77"},{"version":"3.0.0-beta5","sha1":"5a200ee41fcde77f9d6d8510ddb4f7b2e7c19265"},{"version":"3.0.0-beta6","sha1":"531301beb34e0d5853a53952df2300a7909a790b"},{"version":"3.0.0-beta7","sha1":"4ff6bf105e72a07949d1dc253134aef84b74d556"},{"version":"3.0.0-rc1","sha1":"66916b85c4f881348b501352218dfc81dfc1e540"},{"version":"3.0.0-rc2","sha1":"2c89e93d601ca9ea3929750792ff78158c5ab92f"},{"version":"3.0.0","sha1":"243ebf26597dca96c60681106157e27f0762edf9"},{"version":"3.0.1","sha1":"7f51b3e1636063e491131a0b0436512131cb1bdc"},{"version":"3.1.0-alpha01","sha1":"85fe6e444fbc27732e5a1e38effad979d49413b"},{"version":"3.1.0-alpha02","sha1":"c492b2c6c0b573a3deaa57ba08452885f39e09f2"},{"version":"3.1.0-alpha03","sha1":"ef7465eb74415dde2e7112169a831bcc9fa4e494"},{"version":"3.1.0-alpha04","sha1":"ae3287896008d8e73ab35fa1a8da9060a95de945"},{"version":"3.1.0-alpha05","sha1":"32bdb5e824c24fd70c223140c370d912b8596bf3"},{"version":"3.1.0-alpha06","sha1":"3bb3f5388fbf2e93c9886ee446e88fc3f08f06af"},{"version":"3.1.0-alpha07","sha1":"f089019ad7b85002e776f60ba5b0a00ce3ac2961"},{"version":"3.1.0-alpha08","sha1":"a75c5323f3faa6137c67817739f1fb820f184afd"},{"version":"3.1.0-alpha09","sha1":"a6e41cca64aee2bd506a329640ddad063fc21897"},{"version":"3.1.0-beta1","sha1":"c6027be4e50b1d0c58682db0d10aeeb78536adc0"},{"version":"3.1.0-beta2","sha1":"43438ee0ebafbeb1326974dfdccb0e9379470dc3"},{"version":"3.1.0-beta3","sha1":"ac7ae64b7d4f8a271eb8432099be794c76e9b61d"},{"version":"3.1.0-beta4","sha1":"60336efc5a900dfd6b98724410dc880e535f869f"},{"version":"3.1.0-rc01","sha1":"77ba6320986e5d35aefa39db0a782cf7b6169910"},{"version":"3.1.0-rc02","sha1":"419c693a763b4cbd4936c3fe33f9b837ec27ce44"},{"version":"3.1.0-rc03","sha1":"2b9cdc15af335d99a39d6afe0bb632420494e325"},{"version":"3.1.0","sha1":"2a16263739ae9bdd90c86b12ac578a06714ddd50"},{"version":"3.1.1","sha1":"df51ad123c405f0dbd6cbc8774de92ca68afd922"},{"version":"3.1.2","sha1":"be65c11ded4242932046f23ecfa5c7ccb0e98f46"},{"version":"3.1.3","sha1":"4b0f19ce119b0b3efb65a43e8e75ef0697a08dca"},{"version":"3.1.4","sha1":"11005423fee93309c0cd512a8783647702c20c27"},{"version":"3.2.0-alpha01","sha1":"ef859a5445608c517c8c543d108ae12d19807a79"},{"version":"3.2.0-alpha02","sha1":"59b256c161fb930a0b8b7e90a5c6a20aa470a430"},{"version":"3.2.0-alpha03","sha1":"eff9cae7d659311e9570d9fbccec58309fcd55a0"},{"version":"3.2.0-alpha04","sha1":"f81883fb61247373d9780c00a8332436d5fa0109"},{"version":"3.2.0-alpha05","sha1":"7a31b1243a5793c8e618d3636483400c26354b7e"},{"version":"3.2.0-alpha06","sha1":"84cad5f1cc8389ab7fc8ba98a140faaefb390400"},{"version":"3.2.0-alpha07","sha1":"689d0f1be4f7a754345b0c644a71d70ba8e08962"},{"version":"3.2.0-alpha08","sha1":"75466ffa73548a06a3d482b6f9ddb842509802d3"},{"version":"3.2.0-alpha09","sha1":"8faa228067a46836f68a4959d5de87ee1d08670f"},{"version":"3.2.0-alpha10","sha1":"d8bd05b49791c823037075d80492442272521f6d"}]},{"package":"baseLibrary","versions":[{"version":"3.0.0-alpha1","sha1":"cdafca3ede57154435d32b314b1ab1897b10650f"},{"version":"3.0.0-alpha2","sha1":"2e3acd4fe7a55116156d5cfa840440f844b80b8a"},{"version":"3.0.0-alpha3","sha1":"59356761bf19649e6eda0a4172c38d8c0666e32e"},{"version":"3.0.0-alpha4","sha1":"2d688c486b5e88b98b3fba7e9164fa78cc9cb19e"},{"version":"3.0.0-alpha5","sha1":"fcc46ee6349e97d2aa4efd1f8609881000b79559"},{"version":"3.0.0-alpha6","sha1":"564b4ae69e18471ea29504e92f3760c05531abb0"},{"version":"3.0.0-alpha7","sha1":"7ba1330c2b001a4a8772e9b407383ac9dc1661b5"},{"version":"3.0.0-alpha8","sha1":"778d1250e89dd7befe5be0b89c86845dc36a3490"},{"version":"3.0.0-alpha9","sha1":"c58e949ee4c1c0d682c8d8a042a0c3cfadb2ffee"},{"version":"3.0.0-beta1","sha1":"4b434bb7f85f555cf161201f1ff5d2f7adaa377a"},{"version":"3.0.0-beta2","sha1":"7038a7e81af11d1730dc130047bb0890d423ba6f"},{"version":"3.0.0-beta3","sha1":"2989b1d0b8af969d74a77b296f28e45e2e948b2b"},{"version":"3.0.0-beta4","sha1":"8b4f82aef91729ba52f7639032bdf9c7723c339c"},{"version":"3.0.0-beta5","sha1":"cde1d2788e349a1ae8d90575fc7c1956b6179ca4"},{"version":"3.0.0-beta6","sha1":"ba61e4b64de2b4490c2542cfc5fec31135a3396c"},{"version":"3.0.0-beta7","sha1":"35c67c8d091737e2d0cf6ff773a8e89115cc30b0"},{"version":"3.0.0-rc1","sha1":"ef2f13bab635d92a2128612c9402cd6bd8af9564"},{"version":"3.0.0-rc2","sha1":"a41c3297fb57464a47fd2b27ed79fc0ab3e60e84"},{"version":"3.0.0","sha1":"223fbb31236b0714328c415c1475fc8f55a4416e"},{"version":"3.0.1","sha1":"cc0483d84342e82c261aeb5fd0ef2f876b587489"},{"version":"3.1.0-alpha01","sha1":"e8c2e4da2c1a915663e0c274d1e0392083c090ff"},{"version":"3.1.0-alpha02","sha1":"5082912c284ba6c9e4bd384f06de7956174a73"},{"version":"3.1.0-alpha03","sha1":"5d76ef72bd6673b23f39d0e30611c3d076b4e390"},{"version":"3.1.0-alpha04","sha1":"6b62160fc98ff61aa2becdf729cb8844021e61df"},{"version":"3.1.0-alpha05","sha1":"f1a29db9742450d41efc2e10c43f9be5d8906869"},{"version":"3.1.0-alpha06","sha1":"2094bf1a1c47b29f246850571d9bea8761e9853d"},{"version":"3.1.0-alpha07","sha1":"c3021670f169076c564d6834a5000f8690f2cf80"},{"version":"3.1.0-alpha08","sha1":"612f0d8ade947931cb6e8369fb4835441840bfe1"},{"version":"3.1.0-alpha09","sha1":"7ab3c0d5587aaf3d0f677a636021d3551b297986"},{"version":"3.1.0-beta1","sha1":"3f422a7a69b8588898892ac7f46a01881bba0964"},{"version":"3.1.0-beta2","sha1":"ef69679c6313b6a22e6cd61b12e3742300b14173"},{"version":"3.1.0-beta3","sha1":"2c4f07534fc183271bf50cbdedf9b8bcf2d2639"},{"version":"3.1.0-beta4","sha1":"c97831846fca4000490159025654a55669368982"},{"version":"3.1.0-rc01","sha1":"4ae5f7d42e4c9ca9944ee04e1646914dd5ce7443"},{"version":"3.1.0-rc02","sha1":"85b31ad4d36f2b0ac0111677c1508be6c827dc6b"},{"version":"3.1.0-rc03","sha1":"40475a42706216a01fe0aa2aa6b5f94eb25d9fb6"},{"version":"3.1.0","sha1":"f459d107e4331b04b9befd47ab89dfb8fa7aa61d"},{"version":"3.1.1","sha1":"7c7f261e9f8dfb50d888223b13ba32d7622e7fec"},{"version":"3.1.2","sha1":"1b6a1add6a577708b62737dc31c479549f77750d"},{"version":"3.1.3","sha1":"788508016bbd6fa96d90464b58e5284f63366d50"},{"version":"3.1.4","sha1":"df1e7638ba852152abe964e86f60fa4ce12e6a6a"},{"version":"3.2.0-alpha01","sha1":"9c6ae0091698942a8f374e3134b4718812e4d1c7"},{"version":"3.2.0-alpha02","sha1":"81bfb4e06ade66f4211df319560cd134978b2c62"},{"version":"3.2.0-alpha03","sha1":"404a34c3bc3f1a2aa386e8ac49df798f1acb3c43"},{"version":"3.2.0-alpha04","sha1":"98549f3f0bc60baa9c1c96db98bb96ae699de0e9"},{"version":"3.2.0-alpha05","sha1":"1d978f208c28ce4b3d35520c01ded0641956d347"},{"version":"3.2.0-alpha06","sha1":"5a0bd8ac803a491a61f97b1b4bb88353c9bf05fa"},{"version":"3.2.0-alpha07","sha1":"a74e4d8b589d33b011f1bcbc96d7502c32199dfe"},{"version":"3.2.0-alpha08","sha1":"5de955b071c7d145ddb41d7b54d5230bff1b4ef1"},{"version":"3.2.0-alpha09","sha1":"c658921ce8b44eb8c940d8b7348ab3670bac4e11"},{"version":"3.2.0-alpha10","sha1":"ae0a87ffd05204b327dbd4bbbcbd49a845dd6494"},{"version":"3.2.0-alpha11","sha1":"9dc0edc273e2f4e6ac9ceff6dd445866b6313554"},{"version":"3.2.0-alpha12","sha1":"42e1a169145bc3ca675e29712f060558e2c74aef"},{"version":"3.2.0-alpha13","sha1":"7c1d797a16dd60915482f12c95e89d72eedcc0e7"},{"version":"3.2.0-alpha14","sha1":"98cde259e2161a9613dc43397dd3672f6ac4166e"},{"version":"3.2.0-alpha15","sha1":"32a114f11d7fe14a19f1a453143a4d81aee1266d"},{"version":"3.2.0-alpha16","sha1":"bd01ed28963cde4b8cf32b394d9dbfa0c140c0f"},{"version":"3.2.0-alpha17","sha1":"47b3070c2cd8b7e6c731404d213510a49a27fd22"},{"version":"3.2.0-alpha18","sha1":"69c3c9ff8a97bf500637a501953f2afdeed81e91"},{"version":"3.2.0-beta01","sha1":"844c590434be8a5ed905ff089fba00e7c7c0ad7a"},{"version":"3.2.0-beta02","sha1":"56649e969568f1ddef7dd7bf24f32432de365d31"},{"version":"3.2.0-beta03","sha1":"a6dc1763fd6d3778627e350c8f8f0393292b5562"},{"version":"3.2.0-beta04","sha1":"38e335097b4b56e527fd236aa38873d0879dff3a"},{"version":"3.2.0-beta05","sha1":"aa5f61f95df11933b9b43d1865e445ed8ff5474c"},{"version":"3.2.0-rc01","sha1":"5f4af54dca43634ee1c8d3a72a04b09e15d44da4"},{"version":"3.2.0-rc02","sha1":"18aa8aabf4e1ada15f12aaa433559af92fcba5aa"},{"version":"3.2.0-rc03","sha1":"a36b8b3479a4d52fcf805f7d7f46397e2d9a57c"},{"version":"3.2.0","sha1":"fb5f8492c36231104cd86feaefa723291504c0a6"},{"version":"3.2.1","sha1":"62174ecf31e2e3e4dd7a21cbd05399e11d0a9699"},{"version":"3.3.0-alpha01","sha1":"343a3fd7e60910a81a83247f1e49b8556030f6f5"},{"version":"3.3.0-alpha02","sha1":"e17affd248051f0b439dc7b0009cdf05db57e761"},{"version":"3.3.0-alpha03","sha1":"93c771b67630569ca7432add569d9b0c46adf73a"},{"version":"3.3.0-alpha04","sha1":"f417d41a31a1098b3d0d5f8925edcd5e1de3957d"},{"version":"3.3.0-alpha05","sha1":"fd8b2a52fd04a40dddf6a695af94bcf08c7a5e01"},{"version":"3.3.0-alpha06","sha1":"10c7d76f57d6ffeb6325f7140f53989f59f91bba"},{"version":"3.3.0-alpha07","sha1":"11ffd7e09fed884c1ee0c5a418aed05285bdd96f"},{"version":"3.3.0-alpha08","sha1":"a8e57b669e68ecad03bbd793cbcd08abfe65bfd9"},{"version":"3.3.0-alpha09","sha1":"da46d1c7f3288a34180ac789f5ec455b2e18a9f1"},{"version":"3.3.0-alpha10","sha1":"9496a416850b75f7d7d1d73a2c0efdede0fc77af"},{"version":"3.3.0-alpha11","sha1":"a574a0f40c75c25d5a8fb033c8703b0e549ff9ce"},{"version":"3.3.0-alpha12","sha1":"83b5096de68b94c6b94dbb79e3eaf2fe88e935de"},{"version":"3.3.0-alpha13","sha1":"7ff59f587cd541c974082eba1d1381c6dcf82667"},{"version":"3.3.0-beta01","sha1":"9f79671839612fb8c4bbbfce23d45fe9ad17a379"},{"version":"3.3.0-beta02","sha1":"b24c9d26d30c85508e3f89a8ea6ae56310ccd1a3"},{"version":"3.3.0-beta03","sha1":"5174bb43b99e40b275bef13bb577bfc18ea1c41c"},{"version":"3.3.0-beta04","sha1":"e8896480f3de1ee11ce8c33ee7b0112f5da50092"},{"version":"3.3.0-rc01","sha1":"a4bda21b875b83956f9df8360dba9b9802d36f9a"},{"version":"3.3.0-rc02","sha1":"744d6f7ff7e9001983b8800d3121ba321f93f7cb"},{"version":"3.3.0-rc03","sha1":"23b8d2bf0de835affa45fcaab164b00bb4777f4e"},{"version":"3.3.0","sha1":"155f0f7d0e66a992de6a8d73b7cbf71ed22ace60"},{"version":"3.3.1","sha1":"702acc804e85563d3ae7b8ee49122e4f857dde83"},{"version":"3.3.2","sha1":"f47429190ed8581146471c690bb425ac466f4b62"},{"version":"3.4.0-alpha01","sha1":"d312c4f93fe74d3257b89fd00256651ed8742bed"},{"version":"3.4.0-alpha02","sha1":"ab4747a353bc122cf16fd794e7e4a46a400cc30c"},{"version":"3.4.0-alpha03","sha1":"18d63926022e901ff461c4d9044ac14616a6cbc0"},{"version":"3.4.0-alpha04","sha1":"67245e407e5489a2f1645a5c14d42fc1cf9b9881"},{"version":"3.4.0-alpha05","sha1":"45dc96bb5883ab0d9cfdde8e06d86b9c92f09b87"},{"version":"3.4.0-alpha06","sha1":"6eaed8367e85c14a3ca23e618fc5454998b0c0b9"},{"version":"3.4.0-alpha07","sha1":"154e201030405e737e8c560d99144797739f6bf3"},{"version":"3.4.0-alpha08","sha1":"803f3719e305e675a68c08ba53c477d51a9b4c3a"},{"version":"3.4.0-alpha09","sha1":"a880084d2d39f66da56267e37bde3f5f3200eac0"},{"version":"3.4.0-alpha10","sha1":"fa1d82dd282e45294275c088d6b3d25c44596ba9"},{"version":"3.4.0-beta01","sha1":"efa939e7a867f69f2c63f311a6ffe63aacdc6ba7"},{"version":"3.4.0-beta02","sha1":"684cf816e2c727f24b3d8968d6a24efe335c9eb1"},{"version":"3.4.0-beta03","sha1":"b7d90252bb3c08c0f280033ccacb973678516a6e"},{"version":"3.4.0-beta04","sha1":"6aba5163f7ea28fb79718be93bdeed4070309ee8"},{"version":"3.4.0-beta05","sha1":"7300ccdee41f21edb37dffb4f1e3aaf4747729ab"},{"version":"3.4.0-rc01","sha1":"e472c79547043ec992a59525ad36820166263243"},{"version":"3.4.0-rc02","sha1":"565e41da66327b1f4db990413a29767838edcadb"},{"version":"3.4.0-rc03","sha1":"9317042498c16871fd61a2afaaf03306c28e9e3"},{"version":"3.4.0","sha1":"767805331463128dfb118a15813e6ff65290dda6"},{"version":"3.4.1","sha1":"8a7549a34011f871310f4a16d45e70c83289b75"},{"version":"3.4.2","sha1":"8d683cd1a1d859f8ebc629b927d1fa2a3727fcf9"},{"version":"3.5.0-alpha01","sha1":"5f230795a1583087895dbdd897577ae1bf21d401"},{"version":"3.5.0-alpha02","sha1":"a473ed5c74924e56ef530083b00fb8f6c8578a9f"},{"version":"3.5.0-alpha03","sha1":"8a617d320b917d1cef6aeb42a54d2dd0325e2519"},{"version":"3.5.0-alpha04","sha1":"69f1d89ee35a3e51f3829c2f7368cad9d196af96"},{"version":"3.5.0-alpha05","sha1":"83b929945c47131ff61abd26025b8240462a493b"},{"version":"3.5.0-alpha06","sha1":"375753facca2dacb69e72e4d351c5d345060a1d5"},{"version":"3.5.0-alpha07","sha1":"f63550489a11214111f421afbeb3227f61ba188b"},{"version":"3.5.0-alpha08","sha1":"b27a1bef72ffeb0067a9607a69fa90d062cbe80c"},{"version":"3.5.0-alpha09","sha1":"d15db4b2672a5775eeaa69416e871acc3d53b07a"},{"version":"3.5.0-alpha10","sha1":"3aef31c9171022a5e014ed78e5a7931e023780f0"},{"version":"3.5.0-alpha11","sha1":"b7123b05e2c8e1e65896d3ff139ef43e539d99b8"},{"version":"3.5.0-alpha12","sha1":"490698ef585e371b6c31e109bb58fed358c6d358"},{"version":"3.5.0-alpha13","sha1":"6b58be8aa0199e4470e991fb2aa8abcc09f7401d"},{"version":"3.5.0-beta01","sha1":"4a7fc084a5a6244661e64a6aacc5f9873fa52a60"},{"version":"3.5.0-beta02","sha1":"448316cc00dd2325bc11ad2bcbc63c7942dc48d2"},{"version":"3.5.0-beta03","sha1":"e521b5ff89d97e04dc83f8ced31ca2a29e6ef489"},{"version":"3.5.0-beta04","sha1":"7fd7166f079a15a0ae94b440c004af5cb273953a"},{"version":"3.5.0-beta05","sha1":"fa44e389a8c886e32c5c30a05e3eecda736461eb"},{"version":"3.6.0-alpha01","sha1":"b0211fa03db2bd6958a729fd9e836d43bbf9b888"},{"version":"3.6.0-alpha02","sha1":"a71a4c221cea69e60e82d643f4c5dd912bf6fc9e"},{"version":"3.6.0-alpha03","sha1":"701015010889ed75bfac73d77ff820ff14bb6dcf"},{"version":"3.6.0-alpha04","sha1":"5d849d63a8a938d2394c5f21f4493b858fb8b0a3"}]},{"package":"viewbinding-support","versions":[{"version":"3.6.0-alpha01","sha1":"bbab4be2689bc92b8097bafb504a395bc89b9054"},{"version":"3.6.0-alpha02","sha1":"efe0c7e39a714c26d20d29dbe3ced7c29726437a"}]},{"package":"viewbinding","versions":[{"version":"3.6.0-alpha03","sha1":"cc206a5681b2220822f2de1a64c662043e15d97f"},{"version":"3.6.0-alpha04","sha1":"3d6205289c2a3a852df4bef692f6fc685bc61729"}]}]},{"group":"com.android.support","update_time":-1,"packages":[{"package":"support-compat","versions":[{"version":"24.2.0","sha1":"a837404f28f101f2916aa9aa52ee69f66ea3896a"},{"version":"24.2.1","sha1":"30c7a0ee4f95211108dd1de14e6bb0c0488c17f7"},{"version":"25.0.0","sha1":"5095a3c8270bff52f3a4f2a18908c096a8e4afa9"},{"version":"25.0.1","sha1":"fe7a37d44ced207cf0513f1a6a1c6f83ff0a7dd8"},{"version":"25.1.0","sha1":"2bd36a02649a80134b993cee71f4126eecadd536"},{"version":"25.1.1","sha1":"a25cf5ec8c8519cb0ac87434805d036f2fb0461"},{"version":"25.2.0","sha1":"3ae74e452fe09dbbf9f8eeb768213548a4a38bc5"},{"version":"25.3.0","sha1":"ffb76d203b40238d94c2a3a9ed3f8f037dc6108e"},{"version":"25.3.1","sha1":"8add1858a6be417b8bae984c750e67b58d84f09a"},{"version":"25.4.0","sha1":"747a77372b9898a1e3e5deaefce4cb8379ecfa22"},{"version":"26.0.0-alpha1","sha1":"1f61b59d780205187f693db8f62367310e99dca6"},{"version":"26.0.0-beta1","sha1":"8ca0147138efe69fd80fc52d6863334c53e2fce5"},{"version":"26.0.0-beta2","sha1":"9fd199fefe39ddd9b198f52d44f905cb2af569fc"},{"version":"26.0.0","sha1":"a192221305fbfb14e214cbc7546578e5de0ae877"},{"version":"26.0.1","sha1":"398fafa256396e8f5b89bc8087df70f373f2e92e"},{"version":"26.0.2","sha1":"342fa9af0dbea4a4d1d92fabd6fcf55a00528db5"},{"version":"26.1.0","sha1":"1e1c8ffc01d277d8f01dfd11d5d2ce3a2af4b98c"},{"version":"27.0.0","sha1":"1ecb613e79829dcda769f2008054b1feb7d05799"},{"version":"27.0.1","sha1":"df3dabe358f5f29b02cd3eb182f0094fd43c7d53"},{"version":"27.0.2","sha1":"796727c69bcbf62c00f452fbfff878a2497a76ad"},{"version":"27.1.0","sha1":"a7eea3cb0c9f9bf5ce0f0899a9bee721ffd191e0"},{"version":"27.1.1","sha1":"a55ad550d0b84c7fec7ecca012690636062c0e64"},{"version":"28.0.0-alpha1","sha1":"ce81212395942c86808ac33c38de3a120ee63ae3"},{"version":"28.0.0-alpha3","sha1":"6d8cbdfc77abf95ca17625c7cbe92282b47abf21"},{"version":"28.0.0-beta01","sha1":"96b9c729053bc9a3fbb81f947e37e32fc7e75e1c"},{"version":"28.0.0-rc01","sha1":"895318a2039b50d2c705dbedd3761b05461d9728"},{"version":"28.0.0-rc02","sha1":"3574cc3fde35bed13252d5c12e7096f3593f8709"},{"version":"28.0.0","sha1":"d252b640ed832cf8addc35ef0a9f9186dc7738a5"}]},{"package":"leanback-v17","versions":[{"version":"21.0.0","sha1":"e570f4d8e2840df568463d9224ef2c13ca7760c6"},{"version":"21.0.2","sha1":"8405eb152af4f9770aa53cbb4fb9b952b7395e89"},{"version":"21.0.3","sha1":"34c17fb2e1cce4897b6a43072fa900cdf372c092"},{"version":"22.0.0","sha1":"c5e1ba741aa2fd78cd3e34f88eb05e860e1b23d5"},{"version":"22.1.0","sha1":"24bd83bec8466f7197dc24eb6425dfbdcceae112"},{"version":"22.1.1","sha1":"69b9e7a1674f3e4eed4b2486df5d2d22e78603ac"},{"version":"22.2.0","sha1":"22725a9f05e54600065d7973b7ec53107bf54995"},{"version":"22.2.1","sha1":"6ceaca00b98070f725f0ac584d6b5413d9a9f89c"},{"version":"23.0.0","sha1":"1f43d2d1f7ab7183c46517d79c5ca123e4fd74c5"},{"version":"23.0.1","sha1":"ecb14799ea7af281540d351e4efd3cd2b175dd92"},{"version":"23.1.0","sha1":"1c3282bed139374f61bc205f6437d4ed066f0c07"},{"version":"23.1.1","sha1":"c43eaabbb689d100c19927c7a084a83f59e346ad"},{"version":"23.2.0","sha1":"d4910518696ccd768cb00a041e61a29921987419"},{"version":"23.2.1","sha1":"dc2bab692e8472233ae87d2478d914c7700dc320"},{"version":"23.3.0","sha1":"a83f2dd23aa8221114720b68fdbd21c6ba4d7b4d"},{"version":"23.4.0","sha1":"e8558bfac3fb329860d82d99dfac614dce0c5e75"},{"version":"24.0.0-alpha1","sha1":"dae883be7f29e9571c55474f75ba0c2e86cdffac"},{"version":"24.0.0-alpha2","sha1":"ad1ee111c4b24fea84fb4f87201b6605d7772416"},{"version":"24.0.0-beta1","sha1":"9219ea5b3aa272dd6ac8e7e5d596f801da14590a"},{"version":"24.0.0","sha1":"d2228a1c7c5c2fe0c08df0de09863ce1c3281f19"},{"version":"24.1.0","sha1":"d0fc24561445d2ad8d8867354b9942bd0110ee78"},{"version":"24.1.1","sha1":"a2c2bc30ac294bdcb8a2536448e105e539d96218"},{"version":"24.2.0","sha1":"89055c089fc8d1e8f52e8ed943dd83eb732a7c93"},{"version":"24.2.1","sha1":"efec2e5a6bedf649e07a3fbff8da15797b8218c2"},{"version":"25.0.0","sha1":"6cb299b37aa9a2904a95d9b572f31790213c9a7e"},{"version":"25.0.1","sha1":"87712d995142e51893f965b1be1cca39aa81a96d"},{"version":"25.1.0","sha1":"4954d37bf4da7ba6d6a16c29120ea7cb021a4aa2"},{"version":"25.1.1","sha1":"749b98a8c73445215ecdb28fb8780f150702a630"},{"version":"25.2.0","sha1":"761de64d2a742bea83c0eac592ae2d9adff18150"},{"version":"25.3.0","sha1":"e2353a4d9e16c6d83cad1384d66ea1ffc3b3cc06"},{"version":"25.3.1","sha1":"61ec128447f9d3e1182a9b6c442236b709aa124"},{"version":"25.4.0","sha1":"5626787aa33ee096de0a0b90590af7f92485ab80"},{"version":"26.0.0-alpha1","sha1":"f03d4154c88c5f692e4c8745de9cb65f9f66167c"},{"version":"26.0.0-beta1","sha1":"e339afbde9e47abb099952a4e03dedcf7ac1c3f9"},{"version":"26.0.0-beta2","sha1":"f8c51afbf119c4742eab38cfa9c44a41eb995dc6"},{"version":"26.0.0","sha1":"5a7f3e9250044f163d0836faead2f2a810777574"},{"version":"26.0.1","sha1":"3ee7b83f7f5ea31647184c6430572cc4288c487"},{"version":"26.0.2","sha1":"5c8bb06d5049667b768003f19952e7a672b7fda7"},{"version":"26.1.0","sha1":"41def56637a8d7c866319dcf1c835f14186de444"},{"version":"27.0.0","sha1":"469ae411fdb5edb29582235a9c4f3fdc421acb6a"},{"version":"27.0.1","sha1":"97ed74157818a4528f3272fee015cd63389e6ca2"},{"version":"27.0.2","sha1":"d386d67df40ca62da58ce48dc1283f8650e9c251"},{"version":"27.1.0","sha1":"fe1eb6f2bf4cf306dd6265681ef09b838d5d778a"},{"version":"27.1.1","sha1":"20c1f1bc5b8a7b7f434d2a49a4e392d8fad824d4"},{"version":"28.0.0-alpha1","sha1":"eef7ea59e5ca48125dbbdb522a57f32632e7ae24"},{"version":"28.0.0-alpha3","sha1":"78a6b1f1651ec3036c3a4415d18852717741eb00"},{"version":"28.0.0-beta01","sha1":"de05064653e0383a0a8aece92edc4212f95ef4c9"},{"version":"28.0.0-rc01","sha1":"e9fc7108c4ddaef8f2a64aecf458eab820664d09"},{"version":"28.0.0-rc02","sha1":"7dcf426dd38d42a8b726f3312d2400e1a1042869"},{"version":"28.0.0","sha1":"d6c0400075e0abefe97de0fee3da42b896d3e80c"}]},{"package":"recommendation","versions":[{"version":"23.0.1","sha1":"67a5371c5942c14abf38b3b250f93f2dc8e89d3e"},{"version":"23.1.0","sha1":"3a1d887ba2bb14ff226ff137d926cdf9b41fe99b"},{"version":"23.1.1","sha1":"7d6981d75991d2c50c84def723af9d08d2820c09"},{"version":"23.2.0","sha1":"c0b4dca820a8b71dd078496a1d2af6d1d55fa709"},{"version":"23.2.1","sha1":"820dc5dd423c2e809091f6ee7e3980b26b3ebc33"},{"version":"23.3.0","sha1":"330ef782674203d7a1cc6a542221278ae540e8fc"},{"version":"23.4.0","sha1":"ab7f3de505c8c9a0ff6e8243a593c0bc140f30f9"},{"version":"24.0.0-alpha1","sha1":"8cb4b795e7dafa6795148d3f5b2edf19ba09989e"},{"version":"24.0.0-alpha2","sha1":"9c98fee87345b85b21dc2b7809fcdb42c79b3512"},{"version":"24.0.0-beta1","sha1":"ac6f9c6466e589057a14653f3a4baa1fd6f926d4"},{"version":"24.0.0","sha1":"6262d1918f7366d1f135cf6de74d7188dd0b99d"},{"version":"24.1.0","sha1":"1de3f763781997466cb4f83e5c5118d01b429cb3"},{"version":"24.1.1","sha1":"4684ac1d185953dd10a03c4e426dc32f23dc05e5"},{"version":"24.2.0","sha1":"a7f61f21df4e487ffa7e3f052f8fe3931a0e8135"},{"version":"24.2.1","sha1":"77eacc08e943ad87b99de661b72b064a4d01c386"},{"version":"25.0.0","sha1":"abf1a32c00f58c600f075fceba7caa45c3c528ac"},{"version":"25.0.1","sha1":"3335585c213808b30198850fd775a13146a4ea84"},{"version":"25.1.0","sha1":"bba33ecd7562e746a3ffafb4c020dbb121b75abb"},{"version":"25.1.1","sha1":"c8bf2740f36ffdfdbc338be06432e651538a7cfc"},{"version":"25.2.0","sha1":"19ab0ec55b7a7105bb3b5c2f018f65c0fa8ff93d"},{"version":"25.3.0","sha1":"adc8ddec863a9120151af0a13f59f4b0e1824bee"},{"version":"25.3.1","sha1":"510d8610f21d917b42c2f452087193ed5404e333"},{"version":"25.4.0","sha1":"e5e1b91c4ade4169cd6ca5305356cc3781bdc285"},{"version":"26.0.0-alpha1","sha1":"60df327ae9ab02db92b9940a7ff507a5c3f0307"},{"version":"26.0.0-beta1","sha1":"6804c44198590b53dd80959ee5b96dea19734735"},{"version":"26.0.0-beta2","sha1":"706fdc7cd265f4fa1999f2d762231d663af09af4"},{"version":"26.0.0","sha1":"19194fdaa064aa4174af11cb5905d4bb86310f7d"},{"version":"26.0.1","sha1":"6646f5a89325071420e97b1cf4d9b947d764ff9e"},{"version":"26.0.2","sha1":"def055212b8fc3cd6058c40ac7d6a32f0b6ea3e4"},{"version":"26.1.0","sha1":"f829ef63967e4a25f928eee56a4cd23f47c41654"},{"version":"27.0.0","sha1":"24c5045e83d3bb4953a3b72335587f5e9715f26f"},{"version":"27.0.1","sha1":"154a68e57d4421a86235e5850e0de81cf86223f8"},{"version":"27.0.2","sha1":"9d0a267ad9030895029e14731c3b18b20405386d"},{"version":"27.1.0","sha1":"606b4f573ff32b8acfb3b44c8ddcf9fcd72d3453"},{"version":"27.1.1","sha1":"8afe200357826e7e6f9b162cc42a329d5a4a2e33"},{"version":"28.0.0-alpha1","sha1":"331a51bf1e2483a6ad59846ec9dabbdbedc9d7cd"},{"version":"28.0.0-alpha3","sha1":"5775bd50ab5287aed7a5362c4738562dba04022a"},{"version":"28.0.0-beta01","sha1":"1db2b71e530f84b95515c6ca1da8b304fae585c9"},{"version":"28.0.0-rc01","sha1":"729770c6049aa415daa85770ccd26d7d02fe3449"},{"version":"28.0.0-rc02","sha1":"8bfb99fc2e02e34158be5d0c1b0b74ebeafbb1e7"},{"version":"28.0.0","sha1":"7d9e756fd2107c886d0e50cadf4640623bd502cf"}]},{"package":"support-tv-provider","versions":[{"version":"26.0.0-alpha1","sha1":"36acb447122a074e7670689ba93ea8e212fb76f9"},{"version":"26.0.0-beta1","sha1":"e6a1249445310a2265cef910daeaf3d5faa93ab0"},{"version":"26.0.0-beta2","sha1":"de39a870f982e5a0afc48d858d701f738963c138"},{"version":"26.0.0","sha1":"7a6c06d3ae8257f7a7a8e8ad6d3b96ccfdb5965f"},{"version":"26.0.1","sha1":"e2f7979de4a20326fac8886b4458f6d982dbb99f"},{"version":"26.0.2","sha1":"ddd183cdcf889200b8bc6ebb5239667f7795179d"},{"version":"26.1.0","sha1":"77cfbf2193c6417f8ec5d2e84f3944e1f07221c1"},{"version":"27.0.0","sha1":"b1810bca6bbec3b694120fa04c58c4d918269910"},{"version":"27.0.1","sha1":"40bac709bccd196e1514a16ae9121f9ac76300ea"},{"version":"27.0.2","sha1":"e6c841573fcf5cd56c894bb308369ab930394184"},{"version":"27.1.0","sha1":"a6db411a246b3ade61137c0d4427d4783e245061"},{"version":"27.1.1","sha1":"8d1b6580a0479537ba6c4ec43e7c8b2dedb321b6"},{"version":"28.0.0-alpha1","sha1":"1a703808bb6ce3f0b9ae410c6126f6d449895491"},{"version":"28.0.0-alpha3","sha1":"9cda505b288d573ed366cc929a7dcbd6ae2c7786"},{"version":"28.0.0-beta01","sha1":"42f87fe6a70760ed54583e6822645fffe706d542"},{"version":"28.0.0-rc01","sha1":"c54ee987e6da3fb6c8c4edf80a06ad8b351cd0ee"},{"version":"28.0.0-rc02","sha1":"7b0fdfe011656033dfd83deaa650032f5904a89a"},{"version":"28.0.0","sha1":"cfad59432165a7d710d6aced0adef842bbf3f720"}]},{"package":"support-vector-drawable","versions":[{"version":"23.2.0","sha1":"fabbdbe5537cf3a9d07145e6868efd0f8e46902c"},{"version":"23.2.1","sha1":"beb07e7766ea715630831c5611ed42426f4dceb9"},{"version":"23.3.0","sha1":"d1111673cb82b0dae5352636e27b4f1befd08f4a"},{"version":"23.4.0","sha1":"4a568fec3eefa9e8fd1826319bfe18bfb1d54321"},{"version":"24.0.0-alpha1","sha1":"c7a3f9fe7918f46a82380005b33f57614d5c3479"},{"version":"24.0.0-alpha2","sha1":"1536f751b7947b0f8c6cd5fe313a151ec11054c"},{"version":"24.0.0-beta1","sha1":"2fd416c336ce69de5e07a8fd628cffb3cb2da3cf"},{"version":"24.0.0","sha1":"b301036e6bc2b3a6f7949feca6a3d034145d5509"},{"version":"24.1.0","sha1":"70d6835b950ee4036caed72bff558f3f419ab9f6"},{"version":"24.1.1","sha1":"19866ce520d61d4f001aa4c57a8cad3f483b54c5"},{"version":"24.2.0","sha1":"67332692fc6a98ed13dd0c961005bae480b274d7"},{"version":"24.2.1","sha1":"9a7a4708c3274f2f390575f59c5f63981f349c41"},{"version":"25.0.0","sha1":"2eb72277b7926bc22dfcf590e00f249dc3cf9a2a"},{"version":"25.0.1","sha1":"71d90b0c2055f9041286e1ebb5fecde8921637d9"},{"version":"25.1.0","sha1":"99bbef0eb175578e291cc63038df23d602ab0eea"},{"version":"25.1.1","sha1":"8dfbd2e1d5cdd0e58058565d15541a741c232082"},{"version":"25.2.0","sha1":"c4c6179f042f9f02fac0b1be8e475a5605bf2074"},{"version":"25.3.0","sha1":"923e405b0158a3220ce962fd11e2f6bac947e9bd"},{"version":"25.3.1","sha1":"b897d928c149d39b634447b55a6e5bd64bf8073a"},{"version":"25.4.0","sha1":"e65576777f218951e5e1a12bf1de30e48a4b9ceb"},{"version":"26.0.0-alpha1","sha1":"349ffaa8c5705789f1f2e09b0464c41271b7a083"},{"version":"26.0.0-beta1","sha1":"de01a574a2b9fb904db1de118aa2fca4d75a8b15"},{"version":"26.0.0-beta2","sha1":"29a203bcf0a4ad75b8fcc53265ef26ae4337b2df"},{"version":"26.0.0","sha1":"747169593de56d07ddb4b4a9558dccf54bc592dd"},{"version":"26.0.1","sha1":"f1b064f0131d35a270831b581b21061d25e388c"},{"version":"26.0.2","sha1":"89ebac92d36ee264233136fb473d4f61d17d570d"},{"version":"26.1.0","sha1":"1150a1b9610a2d3ac00f81055c862c5d59d95dc1"},{"version":"27.0.0","sha1":"38edce9b920d39156a6363e24e347f354b812108"},{"version":"27.0.1","sha1":"ad85b9cd7f3a8095e9d011f7467ba7f47da79724"},{"version":"27.0.2","sha1":"45a28b4faa184b5c9d607286bfbe33d302d949f5"},{"version":"27.1.0","sha1":"cbfd5d8020f0cbd4b66a09f8d21e2265a55f529b"},{"version":"27.1.1","sha1":"7ffbee6bc80535389f182e559aa279b81b372202"},{"version":"28.0.0-alpha1","sha1":"ab511d8d006c11c7f6aa90c2972d1b7c07813ec7"},{"version":"28.0.0-alpha3","sha1":"e28b088674c1bdde9689e3ab352df12e65e2651b"},{"version":"28.0.0-beta01","sha1":"4b55c233200f90f38df19e61982ac1dcd7266e54"},{"version":"28.0.0-rc01","sha1":"fc92e70de64383d36d8b31a9273b0e4966a84871"},{"version":"28.0.0-rc02","sha1":"e6c309e6f675fc8d5b7c49208a0b9c80dc99f786"},{"version":"28.0.0","sha1":"80387886ef55af284d8253e52d321f93b3f923dd"}]},{"package":"recyclerview-v7","versions":[{"version":"21.0.0","sha1":"601895475140158826d86bed8bc07c0cad6eec2f"},{"version":"21.0.2","sha1":"11c62a8d92fab257bf1bfacd877f53c41dff6c69"},{"version":"21.0.3","sha1":"62a0cc3bb88f2846216bb37ec99ca351d30d6c36"},{"version":"22.0.0","sha1":"e88405610a29f5125b94407e69e566994774f039"},{"version":"22.1.0","sha1":"93579cb9f26015f5330ae645420a289c17afa616"},{"version":"22.1.1","sha1":"bfa82507f81e95cbb88d407f36bed8169e4ec374"},{"version":"22.2.0","sha1":"834f12fed96c923d943752f50ae6fb0edfc6c85b"},{"version":"22.2.1","sha1":"ad0106b4a3c88a56e59550374468f37c611e04ca"},{"version":"23.0.0","sha1":"8959f49563f7531f1d37cf7f8c11b6ffecd64800"},{"version":"23.0.1","sha1":"94d5f16be156521e135295e2ffa70d79ef8ad9d5"},{"version":"23.1.0","sha1":"9baf22ce2d5c1162365bfce00766e47ebd768fbc"},{"version":"23.1.1","sha1":"db18da589eb40f572507a2bd028a281f35d4f451"},{"version":"23.2.0","sha1":"978d278f7d18570fcabe33f43d0c90627b39b577"},{"version":"23.2.1","sha1":"f5c3affb0eca766f258c2d83271ea698153a3261"},{"version":"23.3.0","sha1":"26c4fb8a3fb4ebffc9645eab479889e5485b8b3d"},{"version":"23.4.0","sha1":"61e4d99d2377402c45a3176120f800e53b20ab1b"},{"version":"24.0.0-alpha1","sha1":"f0b0bdbc5e759f892042d37736de3e38985f5f4"},{"version":"24.0.0-alpha2","sha1":"57f2dbd767509fc906fb0ce733074d14e3836a06"},{"version":"24.0.0-beta1","sha1":"1389487ef8617bc185c535393a8dfcf99d59f32e"},{"version":"24.0.0","sha1":"7f284fb9e9e5e58b8cd2c124cab99c6c8f1e08ae"},{"version":"24.1.0","sha1":"cdc6058029f911f989f7a526fe249199f2f079d9"},{"version":"24.1.1","sha1":"eb14224a80834c4eb124970a12e3e46d0a5d20f2"},{"version":"24.2.0","sha1":"1f4a78d3763791bc38414cc2bc3ddbfd51576d5c"},{"version":"24.2.1","sha1":"9d5a6bf44c2fdd2bc1ff478dcec1c5359040f756"},{"version":"25.0.0","sha1":"1d8f0bfa99d79800725daf57e0ba808aa8465ec0"},{"version":"25.0.1","sha1":"584c38397565d36657dc603adb5d2556f6463ebd"},{"version":"25.1.0","sha1":"a75b0b55876b49cf9e7690cb939e0d96946837ac"},{"version":"25.1.1","sha1":"f61731a5b3254fdde4397ab9b746535132fe9748"},{"version":"25.2.0","sha1":"a73acca0b8a2be526bcb4c635ebb18a00fb5e8ed"},{"version":"25.3.0","sha1":"54af5a1612d4024c7656e4bcd59a7578fe1ec5d9"},{"version":"25.3.1","sha1":"38154caf424be47be79afcfeed165b47f1b69958"},{"version":"25.4.0","sha1":"65f7ae7e094909068d0004a1a4ac0fca9faf0de6"},{"version":"26.0.0-alpha1","sha1":"81b2b0dee8aea0cf9133abd5e725cc43aed91bc8"},{"version":"26.0.0-beta1","sha1":"f36b8ef9b8f98e54b66f825411689114e8d9a152"},{"version":"26.0.0-beta2","sha1":"ecfdf89178ccd4a158434846ecd35c57bcadcc75"},{"version":"26.0.0","sha1":"77254d03eb6a7014c5f244675773a0fc3dac668a"},{"version":"26.0.1","sha1":"72c32d2680d98e47e047b6ad70d22df70822dfd"},{"version":"26.0.2","sha1":"d19eefead43b9ec1114232cd697dc96e757ac1c0"},{"version":"26.1.0","sha1":"d0276ee3e2cc61823610236f7e1f76c700847226"},{"version":"27.0.0","sha1":"b4ce734b9e6cc2b730a520efee9771573bb91f84"},{"version":"27.0.1","sha1":"a8eb866818e214fe71244b31ffea07dbeac3fc2a"},{"version":"27.0.2","sha1":"9532159b40e33f05ffc30a74452d324585bf4ebe"},{"version":"27.1.0","sha1":"13765f0bf1fad01dcbdd618bbddd7102408d609f"},{"version":"27.1.1","sha1":"3e9da1c78ef9fac192ff9e614b1c8e943cd9dd89"},{"version":"28.0.0-alpha1","sha1":"855b3c13c8efc24e41775300f97134702e45ce79"},{"version":"28.0.0-alpha3","sha1":"6657e519caabdfbb7813047b7980e79b611c276c"},{"version":"28.0.0-beta01","sha1":"dc12eb7f4bee5911ecf84ee43260c2b04434f844"},{"version":"28.0.0-rc01","sha1":"f6acf380c1930b0519ade152e6dc21e96b5c9c5f"},{"version":"28.0.0-rc02","sha1":"c5a413fd7af0ad92e9de4e25103f0e59769c7ce7"},{"version":"28.0.0","sha1":"d867c38bd39418cc9d734fd60107fe1a2f410c31"}]},{"package":"preference-leanback-v17","versions":[{"version":"23.0.0","sha1":"27989d68a7bee5d1fa8f0f12dd99ae1a4779065f"},{"version":"23.0.1","sha1":"3ced640d4c1112acd5f86b783629fd20b656f8ad"},{"version":"23.1.0","sha1":"8fbe57103a74765c7ef7aab2873d26bca3585a8"},{"version":"23.1.1","sha1":"7d02649bad39c4ef4d71fb95b67c909331447cd3"},{"version":"23.2.0","sha1":"ae2a24944d50dba4f6dd028f17eef884c4d1ca5d"},{"version":"23.2.1","sha1":"821625f437d83ab20a719e3ad7474ffb0ec65b00"},{"version":"23.3.0","sha1":"afc928df1ec3a8a4fac802b5a8e60331f4022fe4"},{"version":"23.4.0","sha1":"e5b4232947a042bf7649bcf9aec5b6a5e838e356"},{"version":"24.0.0-alpha1","sha1":"ad2bc0eda7474fa22935851d40d84c57d52a8dd3"},{"version":"24.0.0-alpha2","sha1":"b5caf52ad162ddd685e99c82f522372d0afa2a19"},{"version":"24.0.0-beta1","sha1":"9b82acf00af3859be8b55353a72418dec9bd1d02"},{"version":"24.0.0","sha1":"fdb4dd39222c433457106b405dc6a8a9e38c45ef"},{"version":"24.1.0","sha1":"563d73734622f4d5d55a23ffce5cd15f6d1632e"},{"version":"24.1.1","sha1":"433e75e009c20a436f6b6e420f5196b1ffaa12e7"},{"version":"24.2.0","sha1":"a9d772eac96886196ec7657433a4b2c7ee798989"},{"version":"24.2.1","sha1":"dfcad21cbe0a2cca3cb7eb2b79b4aa0f4c2b0638"},{"version":"25.0.0","sha1":"3381118cb822c9e0219df59789f5f9c14bbe5961"},{"version":"25.0.1","sha1":"daab8b4bb2ef993abb8d80bdb9b862b335b63912"},{"version":"25.1.0","sha1":"8e0e6f70a46cde6d1710e3de514941559b053b84"},{"version":"25.1.1","sha1":"58d9a44e13b484ae9f3689243da0638ddb38770d"},{"version":"25.2.0","sha1":"641553944d4c4a630a1b12590cae7143825c0ff3"},{"version":"25.3.0","sha1":"48503fccde70ba60471116763bdeb1460e188b2"},{"version":"25.3.1","sha1":"eec3b7bbee25f382189076478516753a7c082c1"},{"version":"25.4.0","sha1":"524399367154206218282e48a0dc6f9e4e02bef7"},{"version":"26.0.0-alpha1","sha1":"3648c7dfd34d78805ac41e39302c587bc966a408"},{"version":"26.0.0-beta1","sha1":"96b21f32142e3eed0b6d6594d59177aa3ea49ddb"},{"version":"26.0.0-beta2","sha1":"54c468276aa44bf1fe59d2bdacbbbd3833dece93"},{"version":"26.0.0","sha1":"70790bf25f78ce1e85f389733bfe598e8ba3ddd3"},{"version":"26.0.1","sha1":"60e5f88c03040f04e643503050016ed1a3354f2b"},{"version":"26.0.2","sha1":"8f5110d7927a19af7ba5ca55c7ee347def976711"},{"version":"26.1.0","sha1":"95d0dfeebea9d30f6df2d0177549f44d1a19253c"},{"version":"27.0.0","sha1":"950fc9c45203a83173c356f8a7f0321c424256a1"},{"version":"27.0.1","sha1":"3000ab3ec01a46947863bdc6b54db6f592f08421"},{"version":"27.0.2","sha1":"dbb22cad7213a5ceeec2c47deeba48f737327a2d"},{"version":"27.1.0","sha1":"b826a77107ae2bd723fc6b814c4f842d0071122c"},{"version":"27.1.1","sha1":"cc9f0923e12de20d45ac34427178291d9b0a9c73"},{"version":"28.0.0-alpha1","sha1":"9a170fbc025c2066b966cc7a8804e0c8dc38cbbf"},{"version":"28.0.0-alpha3","sha1":"355cc86d8104e949c26cbf2c0593af09e046241f"},{"version":"28.0.0-beta01","sha1":"842e1357005c1057a08449e52ed25a1932871a5d"},{"version":"28.0.0-rc01","sha1":"cc128734ed9e4629167fcee0522d33e3b0498fcf"},{"version":"28.0.0-rc02","sha1":"59db003774ae91e3ca5bd3ccd571b97010ccae63"},{"version":"28.0.0","sha1":"5d77b94a4780c69da12deaabf6ccfc10f9ab25f7"}]},{"package":"preference-v14","versions":[{"version":"23.0.0","sha1":"c9646d7bee24cc717bad42ed79acc427f4a84cfc"},{"version":"23.0.1","sha1":"76d54901516000ebe1ace483ec5030370c3eccf2"},{"version":"23.1.0","sha1":"5ac69d492b512191b3b9506bcae1b5d1cfb5eb22"},{"version":"23.1.1","sha1":"fadefa1508061a635068e8828563b6688b708c07"},{"version":"23.2.0","sha1":"2558c8c6ef5690def195f23ced5490d5d9c6f1e"},{"version":"23.2.1","sha1":"b4c5688868349a6b4a3daacf75af6a9f932973ab"},{"version":"23.3.0","sha1":"9657952a4d2e962f9d03fad7c8310b09c13b5e2"},{"version":"23.4.0","sha1":"aca51a31bea5de7752a23dd58acfeff9872bfd52"},{"version":"24.0.0-alpha1","sha1":"878cbf1b78239f944c7f0bcc8fbd50dc3f198d71"},{"version":"24.0.0-alpha2","sha1":"eaf007d306b0db0e12c5d0a8f56e50266b45e22a"},{"version":"24.0.0-beta1","sha1":"44347ac0edf428d70c8b8cd949234f66067192d2"},{"version":"24.0.0","sha1":"aff1a223affe1f7caf51dd978563cb604c2a6714"},{"version":"24.1.0","sha1":"227dde60bae42d62b742467004051b10e806a283"},{"version":"24.1.1","sha1":"3445d3d587b136b6aa90d7dc2c0123c8ec7db736"},{"version":"24.2.0","sha1":"9002b9bded6d4b09ef99ade0e129e56a2ee2fbe2"},{"version":"24.2.1","sha1":"73094484275b967d696fa3ddb46bfc8ba32186c9"},{"version":"25.0.0","sha1":"536abedc29a680bc970e01d34ebb9ee95d92f2e2"},{"version":"25.0.1","sha1":"64474439abb99a1cce425266956a43188231d445"},{"version":"25.1.0","sha1":"7b39f66f6b34f72cc3c6cc4f62578a69c7c9cd63"},{"version":"25.1.1","sha1":"9c30d31b67934cce523747c5c59f5b5fbd1290da"},{"version":"25.2.0","sha1":"19a23f15421034a58a7adf56c97ed199d5205ba5"},{"version":"25.3.0","sha1":"8986fbc91f07cb9144c2c8a65cb910f31c4d8382"},{"version":"25.3.1","sha1":"4db7db91b760271f877beed7d912c35e665226a"},{"version":"25.4.0","sha1":"ec0f058429abff516a472774f0ad7500ee5d5812"},{"version":"26.0.0-alpha1","sha1":"ff810563be37616d39b8b0626d328ca7cf8e49b3"},{"version":"26.0.0-beta1","sha1":"2ce83b6ebaebed22e7b6f5846bff9ae8d75dc7f6"},{"version":"26.0.0-beta2","sha1":"32718c33ef678ae126ead46632fc999c5ed1732"},{"version":"26.0.0","sha1":"b125f0dafff647f4f5f3bf1f7cbea969282e4744"},{"version":"26.0.1","sha1":"c4eec0e252fd5eeb9cb4d0c054c3157107ecf83c"},{"version":"26.0.2","sha1":"3fb1dd7be821481a7cac755116a2f75fb5572e23"},{"version":"26.1.0","sha1":"26a2ec8d5216a491b2710a6c2d4a36b46e636ed8"},{"version":"27.0.0","sha1":"1aca0d0a672735bb401b94afc5294dbd9a1a68d8"},{"version":"27.0.1","sha1":"d9e9e915ff3d12cf4c3262d99a8e8bf616d8ffb5"},{"version":"27.0.2","sha1":"28988fa078c480142216215324a65e54045e3864"},{"version":"27.1.0","sha1":"f8b87d44143a1110070d21bca40976aa14a014c6"},{"version":"27.1.1","sha1":"c3f4760d1e451d33ad7856f3faa680abf1b1d76a"},{"version":"28.0.0-alpha1","sha1":"3ddd8fcbef9ce73c268d5868d0be5e079f04d2a3"},{"version":"28.0.0-alpha3","sha1":"410599554415a067a44759d2291ea1fae737d4d"},{"version":"28.0.0-beta01","sha1":"86cf4b6bf848fc890fdfcbf42a9918d3b634184a"},{"version":"28.0.0-rc01","sha1":"ad492c3711622d9dab7355d424f4ec290ab066d6"},{"version":"28.0.0-rc02","sha1":"26a0c3aba4f48b0b449c2bb6757bc736d6d8c5a6"},{"version":"28.0.0","sha1":"a7c77725c090fd54ef05b0c1e53e1a4421553345"}]},{"package":"percent","versions":[{"version":"22.2.0","sha1":"8e21a52864c9c51463df3896531442708a071d09"},{"version":"23.0.0","sha1":"1835d1049fb3fb5609cd3526e4ed8ae25987dadb"},{"version":"23.0.1","sha1":"75098d635cedf607068224ce8a9c62496dc725c3"},{"version":"23.1.0","sha1":"e7bd198c95e47e9de969390608a3976fd7f9b114"},{"version":"23.1.1","sha1":"3da229e1268745b1a24239dbe99bc48be8e88947"},{"version":"23.2.0","sha1":"310743889a6bdb1fbfbfd21fac2c5b918597594a"},{"version":"23.2.1","sha1":"dd94d4d09e5193da99065f1dfddf50e6095d9162"},{"version":"23.3.0","sha1":"256a5409881c3e2d0d9b0b07ba96d4cf43cee8b8"},{"version":"23.4.0","sha1":"1c64287ddf39432829ac26fe563539156825491f"},{"version":"24.0.0-alpha1","sha1":"afb85f42e21a0d67586f50c245f954555b923173"},{"version":"24.0.0-alpha2","sha1":"cfbe12fee33326f32da478d4309e91c9d12f5b27"},{"version":"24.0.0-beta1","sha1":"6a643a41a9165d3b1e3612a84d8eeb4b797caf84"},{"version":"24.0.0","sha1":"bd6dcb3575f815b2f7016d0cb127eb1dc5ecef9b"},{"version":"24.1.0","sha1":"21d2ca830cd7b7cf0884ac2de99ba2e3ec33ee6d"},{"version":"24.1.1","sha1":"81821204895851dc8ab32a55edbcad2bea2c166e"},{"version":"24.2.0","sha1":"8e4a226621c5173b33215e9f3c73fed99651dd3f"},{"version":"24.2.1","sha1":"4ec14cab6898727ec95ab1048ac0f33d9b97070c"},{"version":"25.0.0","sha1":"f6d54f58cde8b2aca69043e03232b8d2315bd657"},{"version":"25.0.1","sha1":"eb4dc5bb03d6eaeb7807ec4dc0933965c09e01f5"},{"version":"25.1.0","sha1":"3e59d9401456dc059216ea4c3b94216e25a7f2b"},{"version":"25.1.1","sha1":"8ec6b681eeae324bb947dbe9ad94657265f31460"},{"version":"25.2.0","sha1":"6b0b9bec6bf09cd2deaaa416855262007073ded0"},{"version":"25.3.0","sha1":"ee4f74945dbc05f487dd1675ea281ca73984edc"},{"version":"25.3.1","sha1":"771578029294f565abdef7b6e0eb26b0aed6cbe6"},{"version":"25.4.0","sha1":"d275dbc354947351a511f20d1fa838376585f8c2"},{"version":"26.0.0-alpha1","sha1":"ad1d90ec9a1a8adfff81af36364d916047669289"},{"version":"26.0.0-beta1","sha1":"d5dd45f04a551c59a52afa74daf1237b472ae80f"},{"version":"26.0.0-beta2","sha1":"cb0b2913864c45044553719e2213cda0cd1d5a55"},{"version":"26.0.0","sha1":"e5cb0c746f7c77897b53f1a1c3f2604a5a3b184"},{"version":"26.0.1","sha1":"4b6f9a28e7eea465ccbf5e36b281e67dc1cd6e97"},{"version":"26.0.2","sha1":"fe35f292ca9675b16ba406c8a20afdadbef7354e"},{"version":"26.1.0","sha1":"eaf9bf3d9713fbe67196a867a9ba80170565e04"},{"version":"27.0.0","sha1":"9cc6e4de80eccc60c85508e5007c923324bffd3b"},{"version":"27.0.1","sha1":"66636cf08ec38ae9b6f3275871d64265d3ba32e0"},{"version":"27.0.2","sha1":"e2ad0d655ebcc0c23abcda1df56a9fe35908599e"},{"version":"27.1.0","sha1":"4a0bd8350908da20fbd4cea22b1bd240e51a8556"},{"version":"27.1.1","sha1":"3433826f15be121c43fc910bc3c20e7d62c3860d"},{"version":"28.0.0-alpha1","sha1":"39f90d0956701b2ce650aedd063cbbcca72f34b5"},{"version":"28.0.0-alpha3","sha1":"acad75c8c9254f9f7cc9cb896948b4bd7963429b"},{"version":"28.0.0-beta01","sha1":"26c3c0b135c8638228901f3a691c9a06a798881f"},{"version":"28.0.0-rc01","sha1":"a928e3f335a4ba31f3babe763e3bfc129ed35944"},{"version":"28.0.0-rc02","sha1":"adb784502a6d225d46105c103a5926fafbea5b5f"},{"version":"28.0.0","sha1":"4f3e60a57282d52a0b2c9cab57ee29700cb9ee43"}]},{"package":"support-media-compat","versions":[{"version":"24.2.0","sha1":"8a3ea4e6733e4481608c88887b8a75c29491524b"},{"version":"24.2.1","sha1":"b46c989129f773ad6413d5012c3e9373a33210df"},{"version":"25.0.0","sha1":"c223961fd3c4187f48f5b6c6d87df3ad1ef0c028"},{"version":"25.0.1","sha1":"4b4d8c483016d3506bd2e46911f7bdda924b05e0"},{"version":"25.1.0","sha1":"da0bf754ddaf18ba6bb794587d9c56a4b27222cb"},{"version":"25.1.1","sha1":"25bb1a7a7ef149b92b93b5a979d3984b2df58a30"},{"version":"25.2.0","sha1":"bc8ef1a2821831eef3498cd8393078590ef7d1c3"},{"version":"25.3.0","sha1":"e4c005bdcc165031a7a59ee37ba6be728b7fb9f9"},{"version":"25.3.1","sha1":"a2df0839ebcf4721ea0137a71102ac9d58ff4e9f"},{"version":"25.4.0","sha1":"b0ec0aa15e501b547793ef858c2e77eb84e2dd31"},{"version":"26.0.0-alpha1","sha1":"b5adae95c748c36ebdc795db3fc55eb914ba8329"},{"version":"26.0.0-beta1","sha1":"6d902e8996b4699e51e1bcc4593512298f099a5f"},{"version":"26.0.0-beta2","sha1":"a6573c94871842ee9548e361256dd128fed99d08"},{"version":"26.0.0","sha1":"a514ba77895c7a70594c8799dc5b10a236d3e144"},{"version":"26.0.1","sha1":"b7bccc91d4881c1843f43ceb4b081ca70beb7217"},{"version":"26.0.2","sha1":"c7912532b36588f54933bc03b109e5b5f24e0b0d"},{"version":"26.1.0","sha1":"9fb587f27cde19aa8f2e50c5c9ee645d9aec44d"},{"version":"27.0.0","sha1":"ec8b60eaa7778a545c9bc908bef7536d28cca906"},{"version":"27.0.1","sha1":"6654a6aba2c1919febe32a154f0e5df240d903"},{"version":"27.0.2","sha1":"573a444dd7638659b84177799b23395abe1fa97c"},{"version":"27.1.0","sha1":"ad75841e0999df3d8e732930d07ba2463cffd997"},{"version":"27.1.1","sha1":"10e309e2cc22ff4cab30bd5f573e4bb30be707ad"},{"version":"28.0.0-alpha1","sha1":"fec0ab30496de49a19e222eadaba5a8ef8f6fac0"},{"version":"28.0.0-alpha3","sha1":"e360c7a19db409be748f8a75359e3ef9851d55d0"},{"version":"28.0.0-beta01","sha1":"f218142b106bef29708e7c5df2336676ba26074c"},{"version":"28.0.0-rc01","sha1":"c9e64d81ccae1bc270c5e833f4146d88d2cd4b59"},{"version":"28.0.0-rc02","sha1":"c0e8cf7cc681edbf5d661100f14614d7dbe328a4"},{"version":"28.0.0","sha1":"b7ab2145c7f70e303cfe2e44667d61441b5b558c"}]},{"package":"cardview-v7","versions":[{"version":"21.0.0","sha1":"6514ade048c173dd83571cfbaec8c47672c3d221"},{"version":"21.0.2","sha1":"8d397f9e21430e6ac5773d51d49aa4df50bd8f8a"},{"version":"21.0.3","sha1":"850ce66e40362b6eb3da54a51aee8127310b821a"},{"version":"22.0.0","sha1":"695fa515a11241a575bb8f52f251758d627204f"},{"version":"22.1.0","sha1":"3b25d952ed418eec66690717820dbb1320fecfc5"},{"version":"22.1.1","sha1":"8dbe8c91d23b5cc3825d7d967b1e2131a0c82da3"},{"version":"22.2.0","sha1":"e93824375b8f27a11779f7d9675e3b397f14c4de"},{"version":"22.2.1","sha1":"52af0981398dbf7c031af3402f9c8f6aa18ac579"},{"version":"23.0.0","sha1":"4e10821602c3c815b8f9afbb1f0101ee1ad892f9"},{"version":"23.0.1","sha1":"75e30288f3fe849809fa687c441ed2c54f1d4a31"},{"version":"23.1.0","sha1":"9c0994ace3fd3339ea99521e66570adb5df3d5b2"},{"version":"23.1.1","sha1":"66b2abb9ee8d1657ba10ba07396b5c7abbbcdbe2"},{"version":"23.2.0","sha1":"a0cc05f7d0f57961dcccfd0058c0376017775e38"},{"version":"23.2.1","sha1":"1816a5b0392ddc467b321a976cd6d18bbe742e45"},{"version":"23.3.0","sha1":"aa48e70004a4361c9d85f88180723b79681cf92e"},{"version":"23.4.0","sha1":"3fd8efb4b6ac353950f1490891594cfabd692c19"},{"version":"24.0.0-alpha1","sha1":"a849d1f1308442415c1a8db5efdd98bbddc6b3a9"},{"version":"24.0.0-alpha2","sha1":"ca97150ec31f3b578067dd08c8560b590c52271e"},{"version":"24.0.0-beta1","sha1":"e681e16fce3f93970b983fff1eacc7e923aacc63"},{"version":"24.0.0","sha1":"341ed5ed8c9c5bcba3465477c13523b6f15c63fa"},{"version":"24.1.0","sha1":"b2884cd280331ac215e7e3851351058b70bf1f15"},{"version":"24.1.1","sha1":"e6c46712d43f4b424921543829f65b0438670cec"},{"version":"24.2.0","sha1":"ca5f65ac95a1839602c91d0c650d4566c3e8d1f2"},{"version":"24.2.1","sha1":"9b9c96d82268b6deaffeb00303840d3f0d4593a"},{"version":"25.0.0","sha1":"faebfb0f807e66f496817fa2be497bfc886ea187"},{"version":"25.0.1","sha1":"4982f3a88385f943c4128803a2ead9e6081f9463"},{"version":"25.1.0","sha1":"2930dd191cee2da5180d25633de45d449caf19f2"},{"version":"25.1.1","sha1":"b3457632e0bdbc91956820e81df3c7833cc8706"},{"version":"25.2.0","sha1":"5785e97cec86b2384ce2bfd87ee623d06813b140"},{"version":"25.3.0","sha1":"d28cfbc19e0c5a8158239f35e0aee865a6ef6a56"},{"version":"25.3.1","sha1":"bf93a746039a38839c3daeb690256566a92387b5"},{"version":"25.4.0","sha1":"84f80f0cee22c3b01f382148e6d1c16e30a3a4b"},{"version":"26.0.0-alpha1","sha1":"b8ada1fc198b7ef5d0d2f14d2963f8d383becc04"},{"version":"26.0.0-beta1","sha1":"b318516c16897cf894d61e19cf32f8a86c2a3121"},{"version":"26.0.0-beta2","sha1":"f1d0b2ac80dc34130ed90db3c3405ccfacba0e03"},{"version":"26.0.0","sha1":"65368501322f9ff592b1320155fc440301f84901"},{"version":"26.0.1","sha1":"a086a3417b9687fe1a517779554239cbfb0b2445"},{"version":"26.0.2","sha1":"d42d553b43dc1a1a798acbe36c2bbc2b6f3e1830"},{"version":"26.1.0","sha1":"eee9f324d2eac1d64c94cb581c7fd298689f5ec9"},{"version":"27.0.0","sha1":"44cc9eeafc87c0e12f2cd92252156e20878f5ad"},{"version":"27.0.1","sha1":"aec1bfc9820c6d13eaa040828bea41d288a0898b"},{"version":"27.0.2","sha1":"e8ab05ec2c69e608a12aabfd35007ad449f44f53"},{"version":"27.1.0","sha1":"2e861de187605b625626a08dc88a18b941ab1410"},{"version":"27.1.1","sha1":"ba6d14ce05cc71f92cf9cb63771be128b728d22a"},{"version":"28.0.0-alpha1","sha1":"7bbac236c58a948c8f5f50a34aea23273cf00f0f"},{"version":"28.0.0-alpha3","sha1":"3d87eb57641535f57493de577cdddabb71ed7f77"},{"version":"28.0.0-beta01","sha1":"6a7a9b8637ce3de85f4124fde51b26c8c905aa80"},{"version":"28.0.0-rc01","sha1":"9f50cc68b591e337b7ce33734d40e0aad815cc7d"},{"version":"28.0.0-rc02","sha1":"b3eb6f5e3cf82635b7fb00e7086df8f32f1e2bd2"},{"version":"28.0.0","sha1":"2abb6543d6d62659e78974c48121840410ca1d10"}]},{"package":"wearable","versions":[{"version":"26.0.0-alpha1","sha1":"ad50fec1be5b82e064cb3ac4d0b0e10f26b223a2"}]},{"package":"exifinterface","versions":[{"version":"25.1.0","sha1":"b5d7405cca1a869a9e79e7cb983b8a71f22f8ae2"},{"version":"25.1.1","sha1":"9a484cd0a3f68679743cdbf3df01b9d3f0b27db"},{"version":"25.2.0","sha1":"16e4449a10aa372f0722aab8aa9c0555718a6782"},{"version":"25.3.0","sha1":"874cee259930f05501003f5feb545fcf6523eed2"},{"version":"25.3.1","sha1":"1453cd7159c0e35b0eef73c895f759c8b8ad81d1"},{"version":"25.4.0","sha1":"521e1d6f9cac1e88d8258f09c5851ea27243bfed"},{"version":"26.0.0-alpha1","sha1":"63aa38331c3610c3b0316ad04be549e381077ec"},{"version":"26.0.0-beta1","sha1":"8f7bf9eb716524d02bcb0ba1cca1bbc55ebdbf1f"},{"version":"26.0.0-beta2","sha1":"e34e0ee3509d61b116aac19c4bdf0865722f98e1"},{"version":"26.0.0","sha1":"1e6c9b5c584d97a06c0243dbb059ff3c59ce2922"},{"version":"26.0.1","sha1":"466b91502f0e9a66771ced9be29f93addfdef78d"},{"version":"26.0.2","sha1":"45e091576e40b55f0f378ffac3198aa5ad34aeb9"},{"version":"26.1.0","sha1":"c70a4e5f543e519a1feff1414d551b08e2e200ff"},{"version":"27.0.0","sha1":"f21afb769a5554a2254a8e0d99888b747ed5dd73"},{"version":"27.0.1","sha1":"ab84f4566261403c9aa8eb246b239959d179626"},{"version":"27.0.2","sha1":"a64a89990ec0d214074fcd3bf0fef548f5b82c27"},{"version":"27.1.0","sha1":"671dfc354dc01bb2f1b2d1d94cc7598e4ebada16"},{"version":"27.1.1","sha1":"2eafc51ddd22ed3fe6b02447d9b8d188d93fb80c"},{"version":"28.0.0-alpha1","sha1":"202312afdd7bbbd967cac1309c60a361e9f53612"},{"version":"28.0.0-alpha3","sha1":"86a5c0ff2ff8b491edf4488c0d349cb663f2ef9c"},{"version":"28.0.0-beta01","sha1":"aa111bac6cc9ba68e34fd5a11413ef15da9532bb"},{"version":"28.0.0-rc01","sha1":"578f038ef4bbd6105707e058a40b1c9788971a7a"},{"version":"28.0.0-rc02","sha1":"76fce1f5cb10626cd2f9c2dc2a5ef54da92c3142"},{"version":"28.0.0","sha1":"93b15afb1fcff5196284514e3eb2028da2210a06"}]},{"package":"support-annotations","versions":[{"version":"19.1.0","sha1":"5622de20ceb4850c3c1e0ba4749dcaac6cc762ae"},{"version":"20.0.0","sha1":"9d9013e9ff35fc3756411e62873c363c70c638fa"},{"version":"21.0.0","sha1":"1a578b9607b36266c63d43a4fa0ab5664dbe911e"},{"version":"21.0.2","sha1":"99373741f99a0556ee293c41e8faa1ca3ae8a4e6"},{"version":"21.0.3","sha1":"4b74cefe1f0c1b819e7260c8627a14674e37fd35"},{"version":"22.0.0","sha1":"685d0b2c590447e85284ed84712cb363ba04eff8"},{"version":"22.1.0","sha1":"281bc6c58ecb3644c23fbc5ee0452019f67b4d5d"},{"version":"22.1.1","sha1":"1d051a6bfb1218b1db2d3a5af0480ac2c25c42a"},{"version":"22.2.0","sha1":"66b42a1f3eb7676070b7ef7f14b603483aecbee1"},{"version":"22.2.1","sha1":"61a0ff5c29295ec56d7e0bd3d25d95dbf71b4b2b"},{"version":"23.0.0","sha1":"990e7f98a7bad86a40b1f0bcfeb039023afd0839"},{"version":"23.0.1","sha1":"1fce89a6428c51467090d7f424e4c9c3dbd55f7e"},{"version":"23.1.0","sha1":"92e3fc113ec3ee36b64603a38857b95700025633"},{"version":"23.1.1","sha1":"8d680ba5a623724d1fb0e81c36a790f023a6cede"},{"version":"23.2.0","sha1":"c5bf571bb10462bd90e5419ade67793f71acbc87"},{"version":"23.2.1","sha1":"ccb693bc0774fcb637246f7360de25b4af7df318"},{"version":"23.3.0","sha1":"db7791d75a07fef9844d06236c1c59ba7bf6997c"},{"version":"23.4.0","sha1":"ffbe55fdb2bb456b1485831706a9eac3300bb6b8"},{"version":"24.0.0-alpha1","sha1":"b8062546971a3d196343232a19a3c0016272ba47"},{"version":"24.0.0-alpha2","sha1":"8071e1e6cd07e9253720313d6c14286a7ee424e0"},{"version":"24.0.0-beta1","sha1":"4705c19cdec60364bc04e2e928c274da1ba301b"},{"version":"24.0.0","sha1":"2943c74db6d4346ab8c6531adac91fa05c137c40"},{"version":"24.1.0","sha1":"8a23146e1a8c7cb4758e3d3f62449213f1e51f67"},{"version":"24.1.1","sha1":"3af19f153122737b372622fa6c81dd11a1c6b999"},{"version":"24.2.0","sha1":"6be858a75633b1f0a2d24cad740db57775cb6871"},{"version":"24.2.1","sha1":"8de3aafecbdf98a61db6427f008f2d515bcd2544"},{"version":"25.0.0","sha1":"a22d2cc94da87b254451fa8ed28afc30b4653773"},{"version":"25.0.1","sha1":"a09194311f8f89c3e6db09d79f50897a889d615e"},{"version":"25.1.0","sha1":"64cb6b229a8bdfd907bc8dd46839a0053e4c018d"},{"version":"25.1.1","sha1":"b59b6ee8d6ecc03e672d253800430343493a6ed8"},{"version":"25.2.0","sha1":"4a5a82a027706397fff621b22fd6fa796e91d7c5"},{"version":"25.3.0","sha1":"dd84903d2e1af25a6081ef9038ad863cb14ca3a0"},{"version":"25.3.1","sha1":"bf8841d29fa39e077730e7a91222c2ce269fa7c9"},{"version":"25.4.0","sha1":"f6a2fc748ae3769633dea050563e1613e93c135e"},{"version":"26.0.0-alpha1","sha1":"23ee503b631f69511e335a061ea008f8da60f40"},{"version":"26.0.0-beta1","sha1":"419585951f4c634aa292db0b734c29541a25b94a"},{"version":"26.0.0-beta2","sha1":"e5ef6e4822404221c9b03dff0d31e4810ac3fb0d"},{"version":"26.0.0","sha1":"db09a97f4f0db8892c1a0111a4b966f97920d082"},{"version":"26.0.1","sha1":"e1bb32b12b25e2f78e6786059ca7ea878f8345e0"},{"version":"26.0.2","sha1":"8b68a849722b44f584e2d68c451c5e3844c10380"},{"version":"26.1.0","sha1":"814258103cf26a15fcc26ecce35f5b7d24b73f8"},{"version":"27.0.0","sha1":"a7782471b2a0de8fec071b8094a8aaf61c46ab70"},{"version":"27.0.1","sha1":"287742f1c6cea6d9126670e9f031890b0462362a"},{"version":"27.0.2","sha1":"b9ef4342c934a1a8b107506273dc8061662a322"},{"version":"27.1.0","sha1":"39ded76b5e1ce1c5b2688e1d25cdc20ecee32007"},{"version":"27.1.1","sha1":"39ded76b5e1ce1c5b2688e1d25cdc20ecee32007"},{"version":"28.0.0-alpha1","sha1":"39ded76b5e1ce1c5b2688e1d25cdc20ecee32007"},{"version":"28.0.0-alpha3","sha1":"7f23bd602124f31bda99edc5ee8a3bee209dae67"},{"version":"28.0.0-beta01","sha1":"30b42ab62f30117536a5e37570719107c0d1c44f"},{"version":"28.0.0-rc01","sha1":"4fec72569449246ee917f3249147f8e47910b119"},{"version":"28.0.0-rc02","sha1":"326b0d8c3d8dfe86ed237f0a74395ca8d347e561"},{"version":"28.0.0","sha1":"ed73f5337a002d1fd24339d5fb08c2c9d9ca60d8"}]},{"package":"appcompat-v7","versions":[{"version":"18.0.0","sha1":"c28672362f261d396dec8dcec35e2e92562ac2ba"},{"version":"19.0.0","sha1":"1511a386a3ab25a6b2920384f9934d2b57b80ecd"},{"version":"19.0.1","sha1":"3e02ef4af657c3bd4197ad518d85071eb416cb55"},{"version":"19.1.0","sha1":"af7fda77d88b0098d371ecfa22e7de96220dbbfe"},{"version":"20.0.0","sha1":"f8692243a72fd0f64971cb0ccc186861587e4102"},{"version":"21.0.0","sha1":"4a3b6d98f08840acdc6103a753c1e1da3603da22"},{"version":"21.0.2","sha1":"eb706aad019dd37a21d6359aba1236f3c64e9344"},{"version":"21.0.3","sha1":"7890a293c283e2668b0de004b756a3a88733e3d2"},{"version":"22.0.0","sha1":"c0b903b2bd54c664bcd69b74a15b243b015271fa"},{"version":"22.1.0","sha1":"24b50583000e776a666062b16f9e6580ea46bbe4"},{"version":"22.1.1","sha1":"950e5c5c588164283effca8bb8a253293f150cb4"},{"version":"22.2.0","sha1":"e4a5a6d4cbf6b34fa012865e7660310943d59d20"},{"version":"22.2.1","sha1":"aa102ba7444b18a1d702d01eeb14e852e740b26e"},{"version":"23.0.0","sha1":"eba5cc0b145ef46b18afe44b21e0d39feeb03c78"},{"version":"23.0.1","sha1":"7d659f671541394a8bc2b9f909950aa2a5ec87ff"},{"version":"23.1.0","sha1":"ec99fae8716984ec56489fb45d1ae752724bae7"},{"version":"23.1.1","sha1":"8bcc1fd66fb28dad3ae95d753c36e99749c6b88f"},{"version":"23.2.0","sha1":"29105df887f5af8067aaa2d05d058b5f72785dcf"},{"version":"23.2.1","sha1":"f59182dcd649dae5597bd465198a0fd16da27979"},{"version":"23.3.0","sha1":"77775419089d1a5aa60fa159321116b9131674f8"},{"version":"23.4.0","sha1":"a6c729c7a95b8fb214c4d89d298ce242e71384b2"},{"version":"24.0.0-alpha1","sha1":"f9f7b7c2977fbf2cdd8c9764fd167500a9e53ba1"},{"version":"24.0.0-alpha2","sha1":"743ec5010da136c053c2945880ea71232bfd6fe5"},{"version":"24.0.0-beta1","sha1":"5baae1012638cbb679aa324118403e22083422c1"},{"version":"24.0.0","sha1":"56f1c949431817df40e342fea3e2289366716a42"},{"version":"24.1.0","sha1":"d475560fd637ea68e710f9a58ae105799ae85046"},{"version":"24.1.1","sha1":"bb70822533bcab50a2d6b5efc0fcc0647dc7e418"},{"version":"24.2.0","sha1":"79a54262889558b6263449437c6aac4e491484be"},{"version":"24.2.1","sha1":"95444322e0a563ee4934b6ef09f346c50d2c2940"},{"version":"25.0.0","sha1":"4f22937dc286e9751f89c7eca9858a559e90cc1e"},{"version":"25.0.1","sha1":"cf4e106271f2ad88bfcee55cda5a7632751c877d"},{"version":"25.1.0","sha1":"a3a78c284d229be4a4b2c45b37ef94a685ae11dc"},{"version":"25.1.1","sha1":"4a61d0c7030ac79b1bfa8ef0ba6014de405ecb84"},{"version":"25.2.0","sha1":"cede75671ff307240a2ebf66c362a922e1bf2b1f"},{"version":"25.3.0","sha1":"4ad8ef6766bead18875e5f3ad8e8f8bca8d15b97"},{"version":"25.3.1","sha1":"99ba6ed8d35e089c35ce4d6a09d083a2829a81b3"},{"version":"25.4.0","sha1":"db5f5c97f25ba8dde135858e48b027781f4cee4e"},{"version":"26.0.0-alpha1","sha1":"7b70ee02dac005085d823a27792b03ef9356b3b3"},{"version":"26.0.0-beta1","sha1":"47a7cc86ebeb1be2030e6bc8c26691884cf46bce"},{"version":"26.0.0-beta2","sha1":"a0367a5479a226c644f71b056c5b36253735091a"},{"version":"26.0.0","sha1":"bdc6c8fb723262e43e4776fec6170db5d1692fcb"},{"version":"26.0.1","sha1":"b0f32cea48dbbc77968a37718a9188387e992fea"},{"version":"26.0.2","sha1":"2950c96bbe00b54626a0b00439e9b1f767518ede"},{"version":"26.1.0","sha1":"da5771a9ac6e8f3a461360eed3c6f921dc7580fd"},{"version":"27.0.0","sha1":"8dfefa90131043b62e6bd506ba91df3cee1f248b"},{"version":"27.0.1","sha1":"2fc21de3a59a1183bead10473a551e43c6af8cef"},{"version":"27.0.2","sha1":"87c4b381cb7e6baeb254643b74a11b0472c3c953"},{"version":"27.1.0","sha1":"41d5bc28ca11447234a8b9ab5fb133eaa3cc7fa5"},{"version":"27.1.1","sha1":"22b1ef4ff9ef1a3513c18eb132d597eac6ef1a86"},{"version":"28.0.0-alpha1","sha1":"2eb478a1d4e31fdad1c6f912c735b180d3e0c913"},{"version":"28.0.0-alpha3","sha1":"d5a23fa8047cb79f38ac1ab77570c4ba10b14b15"},{"version":"28.0.0-beta01","sha1":"dd041a5374cfde638aeca8e4476a5775208d7d13"},{"version":"28.0.0-rc01","sha1":"4e333b67fed76846ed14b0367a259dc3ead06423"},{"version":"28.0.0-rc02","sha1":"afd52f508eb2986f41faa3837f7b2fabbce05ac3"},{"version":"28.0.0","sha1":"132586ec59604a86703796851a063a0ac61f697b"}]},{"package":"palette-v7","versions":[{"version":"21.0.0","sha1":"a590b647ad7223e82e3b4241fa7691310c1c9dcf"},{"version":"21.0.2","sha1":"c9ca380f13980bbc3a84f5a14716e824536a2d0b"},{"version":"21.0.3","sha1":"ad0bd7ed69ee9abf278ec339ac4bccabcea8996"},{"version":"22.0.0","sha1":"1444cfa883a1d2daa7b434f88887853835ff7abd"},{"version":"22.1.0","sha1":"a38e7cfadfc35167c61fbc0ad77088a3203b9c6b"},{"version":"22.1.1","sha1":"1aad3dbaed2a7b963369624d5964b0753825f572"},{"version":"22.2.0","sha1":"d997c9829b713fe7d2ac33f628e5323e3647aca8"},{"version":"22.2.1","sha1":"97258858493eaab3542496232312aba652f79b7b"},{"version":"23.0.0","sha1":"989b70ff93eada0e9d744b42871905e7a537fbfe"},{"version":"23.0.1","sha1":"9e107da9b550f7c73d66859c44aafb9b9252d87a"},{"version":"23.1.0","sha1":"a2e4ab66075775eee4775cfd683cb0dbaa733930"},{"version":"23.1.1","sha1":"b8864acc904d2dd383085d9d363e67e0b73e7985"},{"version":"23.2.0","sha1":"9bda889956c6e2a8b3b7d577a68e251383173417"},{"version":"23.2.1","sha1":"9c9e55df4435e56f2e9d65d8aebfe6b179e4fb1d"},{"version":"23.3.0","sha1":"ee7c3b81a4aead81d9e57ff7002ce272ac29854"},{"version":"23.4.0","sha1":"6787d450aad71c9851f8ca13fe8605cb7d3968ea"},{"version":"24.0.0-alpha1","sha1":"873534b9ca890413ab44fcb67ab7d389b52b96df"},{"version":"24.0.0-alpha2","sha1":"48dce37d0a5780f25d9bdbb9ce89593803b94e5b"},{"version":"24.0.0-beta1","sha1":"3d518e77cc7024a80a988add6e5c3e888bd03df2"},{"version":"24.0.0","sha1":"11ea7b08030044cc6e130071daf716da98479fd8"},{"version":"24.1.0","sha1":"1fe121a00590528bf78450428f2907ec50f48f40"},{"version":"24.1.1","sha1":"784b01de5116d10fc09d4ba1b6f64f05276acd29"},{"version":"24.2.0","sha1":"7ab2a294487f060da195de76faea61e5be96a4a"},{"version":"24.2.1","sha1":"e998513782cf44ee61d5b13203639e62085e32b8"},{"version":"25.0.0","sha1":"f20b855003b9679040607f7e85de684db15bb30f"},{"version":"25.0.1","sha1":"98c316e30454b9ae3bc0fc994455890e49109172"},{"version":"25.1.0","sha1":"8c48d59ffcbfdbf865132b3d66ac8f9891fd35b7"},{"version":"25.1.1","sha1":"3c46de5df81cdfad450c76863318fbfa06900c7c"},{"version":"25.2.0","sha1":"1be5d42bdbd146b201d1a7e36278cdc5581a6cda"},{"version":"25.3.0","sha1":"b920a84b0da4c23d176f935e5bbe05acd3938292"},{"version":"25.3.1","sha1":"9cb5935db5cdf0e85779c35405d858c47042b4a7"},{"version":"25.4.0","sha1":"f0a0fdd9c488bcf2a2b745a7209362c89da67396"},{"version":"26.0.0-alpha1","sha1":"f286a5dd4f370d2824b037e726c55cf1d3c1c29f"},{"version":"26.0.0-beta1","sha1":"3ef22267b14b33efe0326cb4dc847f659202f457"},{"version":"26.0.0-beta2","sha1":"c428e83d7d1a8776e130a63da9a82b7212823e88"},{"version":"26.0.0","sha1":"2d1e66acf18f8cde0c9ab285ca45b6ffc8bd1b76"},{"version":"26.0.1","sha1":"74b55108b3d8d5cf52cdbb4a5909b5d633bc906f"},{"version":"26.0.2","sha1":"28254f0725f8ae452d652a8abba31d5bd53699c2"},{"version":"26.1.0","sha1":"acbac5347bb4d79a3d3f95c992ff6bf49885bf84"},{"version":"27.0.0","sha1":"5d3a9d3eb22e4536592e0c438ef5abce4627aca9"},{"version":"27.0.1","sha1":"fbb1d51392ed8ea76fd4f4ad40c3e8e3d8d18725"},{"version":"27.0.2","sha1":"5f00252925f146843cd697ac1144aa09981e75f1"},{"version":"27.1.0","sha1":"10d92c3cab5940917dc547ea9a1b41a7fba35add"},{"version":"27.1.1","sha1":"6871deecfe87d3243ae36858b94d3ea5fcad6463"},{"version":"28.0.0-alpha1","sha1":"3c18ae1a034d960dba97be0401cc013b3b4d81cd"},{"version":"28.0.0-alpha3","sha1":"375c612adf941e2ee9651aeb6d9f5e39e1de4eae"},{"version":"28.0.0-beta01","sha1":"e97f27819f331dcd54f00375e26f5447c7df7743"},{"version":"28.0.0-rc01","sha1":"35b4c9c54c4860a99f7ae2dc141e842da7711a86"},{"version":"28.0.0-rc02","sha1":"788309cd207135e0252a20e3b3521de524a20c91"},{"version":"28.0.0","sha1":"dca541d3c7e740434da5cf40442367fb920017a3"}]},{"package":"multidex-instrumentation","versions":[{"version":"1.0.0","sha1":"eb447e7ee41bf43190992c240c3fc2adc2e28b61"},{"version":"1.0.1","sha1":"ca8704e84b2d261b4028fff0457a42cc9a0492e3"},{"version":"1.0.2","sha1":"3f6d05031d1aa62ecb5bffaef4c65b297dc3be17"},{"version":"1.0.3","sha1":"eb83e83ef2aaf9be25d50932577371415cdd300"}]},{"package":"multidex","versions":[{"version":"1.0.0","sha1":"212324cbd90e141615c193bcb051838c3b7f4d9d"},{"version":"1.0.1","sha1":"60d4a46aa19ed61f4903ca2e0ad9e959eac06ec5"},{"version":"1.0.2","sha1":"615a95b001b4e3b3bd5743106379e62f6290b47"},{"version":"1.0.3","sha1":"977baa430cd9596838e8b1799b66ecf4d10a723b"}]},{"package":"mediarouter-v7","versions":[{"version":"18.0.0","sha1":"4ed433dfd59099113a698116525fbf5c5a198c5"},{"version":"19.0.0","sha1":"eb5f5824d3767e75c057db28af44c255ee4acc1"},{"version":"19.0.1","sha1":"f87107bbdaa21468a0203e331dd6b7af64d26747"},{"version":"19.1.0","sha1":"23e16136cbec55b21862cf4cd6bbee679273b8"},{"version":"20.0.0","sha1":"cb6761116a302c65adf53d8920f5c6b18ab08b03"},{"version":"21.0.0","sha1":"7b3966956669546b19e466dc7c7975458b0a66d2"},{"version":"21.0.2","sha1":"bb2bd11d17fc6e59458ae797cf93bde6ccb790ce"},{"version":"21.0.3","sha1":"4228ae756e55db25add90fab4115799d0f8a673b"},{"version":"22.0.0","sha1":"9f2a007213b7caf9129b28c2cc2af4db9eac1fea"},{"version":"22.1.0","sha1":"6419a4280c83fd29e8ae71b34df90a9ef3b87467"},{"version":"22.1.1","sha1":"f627bd011f86f6c0c8dfa06bfa02f401d3a1034b"},{"version":"22.2.0","sha1":"fe05cf853e89a6f4df03e4daa9a8e20de213a2ac"},{"version":"22.2.1","sha1":"255f1401e5cae8a0d38e11006803db4fd1ed21b7"},{"version":"23.0.0","sha1":"a04883bc94fb96d9d0e19880df6a9eb3cb237f9d"},{"version":"23.0.1","sha1":"f4eae71aabfa4c7f23aed80af6ba92fc929309a7"},{"version":"23.1.0","sha1":"33ce1b6b7abb2afe0a081282d702347cfc4e297c"},{"version":"23.1.1","sha1":"764a0f651f1de93edc0d8b8077d8388adaa01ae2"},{"version":"23.2.0","sha1":"5757a94a293d3fe33e0bc3fbc510ac11156af6c6"},{"version":"23.2.1","sha1":"98bf6a9495f25f3b914583d2a1e7e9b6d5810078"},{"version":"23.3.0","sha1":"4d05098b76c597216373a71e4bae9c67e0da36cf"},{"version":"23.4.0","sha1":"6deda7d51e36d94b6630a9d461d468ed82d3ddf6"},{"version":"24.0.0-alpha1","sha1":"fb7ae534cfceaf022bb0d420acaf8f06fd31cb5f"},{"version":"24.0.0-alpha2","sha1":"f02db0de264580ea0f9864d944f226d9c16b5118"},{"version":"24.0.0-beta1","sha1":"3ef27b5540bb8c705057591aa10899d9a8d8983d"},{"version":"24.0.0","sha1":"baf58a0c74e7d91983e1c2c5fc865a118ebcb84a"},{"version":"24.1.0","sha1":"41ea9d115989b343ea5f0e36e97c10a0d6d46d8a"},{"version":"24.1.1","sha1":"51c476adacb1cba8642d0763a275614d7f960940"},{"version":"24.2.0","sha1":"5a47475305a4751c6ca2d12987a67d5733169dcd"},{"version":"24.2.1","sha1":"2cb605bde154e2e7294a2c203ac10be0b5f2313f"},{"version":"25.0.0","sha1":"26f51f0db2b65924d010a295b323d5dfa321d20f"},{"version":"25.0.1","sha1":"b54475b8faaa13fc20398e2953cd145942a5f090"},{"version":"25.2.0","sha1":"9d8d254e4cb68bfc84abb7fe1c00dfdbd112f04f"},{"version":"25.3.0","sha1":"50259ee1dd7b0276e1133eb793049f417a6bb41d"},{"version":"25.3.1","sha1":"3cb013f11a71ff901a63943fdffe220c6cfcfdd4"},{"version":"25.4.0","sha1":"bb0b9b14dc3aded3506494ae9b43eb621f4f4e89"},{"version":"26.0.0-alpha1","sha1":"52841f5c436e9108558ddb8aeb9cf807b84bbd73"},{"version":"26.0.0-beta1","sha1":"10a04fdeef82c19bd481fde94f9f44d921c30926"},{"version":"26.0.0-beta2","sha1":"415e8870a9bb759cd97cc4521bdfa80b2e0b92aa"},{"version":"26.0.0","sha1":"a1289084073af50829adad7918d52845d4ac6bf6"},{"version":"26.0.1","sha1":"315d181d333380f9f8e60122b9b0dac164443852"},{"version":"26.0.2","sha1":"140d55b9aadfb7c2792d1bc8571162d540677346"},{"version":"26.1.0","sha1":"9fbf6c99ddfc7004024255eaa59eadadc717a0ca"},{"version":"27.0.0","sha1":"cf09b469e5db852c5d8225aca8b57c95128d4ab5"},{"version":"27.0.1","sha1":"4b8932966e726b87fbd4ad10837f0cf55ae725f8"},{"version":"27.0.2","sha1":"503e82d5c7250a780a72c15247a07de755487773"},{"version":"27.1.0","sha1":"dbc8c5cde250974357642854188cbc82856acec"},{"version":"27.1.1","sha1":"433dcaebd8f3bcc950475737e2170e488820479d"},{"version":"28.0.0-alpha1","sha1":"2cc1439fb53ac30672d1d05e962b18fab7e8ea13"},{"version":"28.0.0-alpha3","sha1":"b527ae8b215aa7c984218d53c40e861581529fa8"},{"version":"28.0.0-alpha4","sha1":"92215cccabbc215a1d9f1d81558d985580a5c3b2"},{"version":"28.0.0-alpha5","sha1":"e9b0b8022077a2c59002d47bfb90434e0a15996c"},{"version":"28.0.0-beta01","sha1":"5d5a3515fd9d605ff6f7d9b2228ce50aa27c5649"},{"version":"28.0.0","sha1":"77edeb1eb070e488dc6c2d22e8931657a15e0083"}]},{"package":"preference-v7","versions":[{"version":"23.0.0","sha1":"90747be6dee4b73cf127056e450d565442586b74"},{"version":"23.0.1","sha1":"59c5427f245f690bf9810218f5ff80655b66dedb"},{"version":"23.1.0","sha1":"993f4622325baf828e2f5976e43b471143ad976e"},{"version":"23.1.1","sha1":"d284fef0757424c59f2ac5ff759fd7ccaacffe15"},{"version":"23.2.0","sha1":"db87d123141fef4d831f122177978ec8deea0f5a"},{"version":"23.2.1","sha1":"7d785ada6bf9de6c35958ce627518036dddf775d"},{"version":"23.3.0","sha1":"d8f5fca1c6e397d4784acdbcb9a151e4cf825aa1"},{"version":"23.4.0","sha1":"20a42dd49a9e441e212d81326c7f0f88beac9032"},{"version":"24.0.0-alpha1","sha1":"1ac00f510d587246636032a35a230ad601f90fd4"},{"version":"24.0.0-alpha2","sha1":"fb7825d2c4fd668cbf676e8006a651ffed7c9abd"},{"version":"24.0.0-beta1","sha1":"80c33078bd14a56876ccce06919640ec2961caa5"},{"version":"24.0.0","sha1":"acbd6046e649b04b962f4a6ba38f7c408f51acc5"},{"version":"24.1.0","sha1":"c4147ba89c7fb2700cb7527a8df0140ce3ce7e8"},{"version":"24.1.1","sha1":"5687e6a8ba75e229fe6b5a026557f0af541e7bf"},{"version":"24.2.0","sha1":"a73e61fa11ed7e8635b1f95572758ccd5da7cb0d"},{"version":"24.2.1","sha1":"2474aff058d76752bcdd360e9c27fce76463b795"},{"version":"25.0.0","sha1":"b9ca7d4a9424544df6a31d10ac980839820c2c0f"},{"version":"25.0.1","sha1":"c877cf6b2ed8ec2b529db5f4c11cb93601d9c2c2"},{"version":"25.1.0","sha1":"7cf8642fa669a23781c7d16918c27b4e80c98f7c"},{"version":"25.1.1","sha1":"9fe59b4c73fb584cfd6f1447741db793657edf48"},{"version":"25.2.0","sha1":"72801af60835eaed52bc3015fad5e720559640df"},{"version":"25.3.0","sha1":"e09e80de5539268dbed04bfda944df4db1c0c783"},{"version":"25.3.1","sha1":"ad4b15ab86372d7e08ff884a84810228181dd34c"},{"version":"25.4.0","sha1":"32867d1b095882ceba4ec2dc40b50f1b4123e98"},{"version":"26.0.0-alpha1","sha1":"6cd16165107c4ace76a7f232b4257e671d58f494"},{"version":"26.0.0-beta1","sha1":"6ef9bae60d4dba56e20a6d70b47ec5153a609c58"},{"version":"26.0.0-beta2","sha1":"8998db26946b5351d1b0aefae7fd56ad74a57cbe"},{"version":"26.0.0","sha1":"856d3c9c58652ce7d160d07689fd61e57bda0193"},{"version":"26.0.1","sha1":"2165f54e68c31be566362b64327847206dbb40f2"},{"version":"26.0.2","sha1":"de87be6f39bf0137f2a7d24564f4cbab251b162f"},{"version":"26.1.0","sha1":"4f4fa568b36e3f41558bd01f834b0b1cbbef6528"},{"version":"27.0.0","sha1":"2c7492c7f2648f3521c5b999010e54d2c5df9791"},{"version":"27.0.1","sha1":"bddafb1df325ee0812f94f3f5eb74ce7b73e1ea8"},{"version":"27.0.2","sha1":"e811d1e02d80dfaa2564aa14d7b9035a28318e89"},{"version":"27.1.0","sha1":"3b17cd06ce24d247fb39e7b4c001344030af7568"},{"version":"27.1.1","sha1":"5f863b5aafc8043751eb820d3780bd196228adb8"},{"version":"28.0.0-alpha1","sha1":"bc45a18bfb231e8a902e624454111e2345315777"},{"version":"28.0.0-alpha3","sha1":"86f4116cb031df227dd3485384cc76a3c2b12200"},{"version":"28.0.0-beta01","sha1":"100e66c85735e722ff84eb543e1a116dbd4449bd"},{"version":"28.0.0-rc01","sha1":"860f1b92bf22017472b4eeb342ce2ed262a4ce79"},{"version":"28.0.0-rc02","sha1":"5e1cede815c209732d61a8fdf683731d352281d"},{"version":"28.0.0","sha1":"9abe7c5c01d5ff9549ac877c9ce368d1e75d2d22"}]},{"package":"support-dynamic-animation","versions":[{"version":"25.3.0","sha1":"3aec2ed659bf61b0f5924fb409cf2b6561b828a2"},{"version":"25.3.1","sha1":"ac709e22ff5c728a7bb48bfe63708b2b2fe26a4e"},{"version":"25.4.0","sha1":"d02536f6cbcf717e58d3761043d240cee9bae0bc"},{"version":"26.0.0-beta1","sha1":"57d2050b6336d9d9bfb0e2d9c48a8a7fcf9e23f3"},{"version":"26.0.0-beta2","sha1":"281d9063fecfdb8a5bc92909285f1f24c1afc149"},{"version":"26.0.0","sha1":"381a3bc2de4008e7dc4aedbc6add8b9f1047e44f"},{"version":"26.0.1","sha1":"da3c11a0bf80ce8c28f41e4c90c3e4f843be916e"},{"version":"26.0.2","sha1":"20230de583fae449ec34586de9bb8573a1ed47c4"},{"version":"26.1.0","sha1":"69cf22368d1df5907d1e7fede1cacd98ce88287"},{"version":"27.0.0","sha1":"b3a48cbb191ccae3fc1fce958960dfe470cfba6f"},{"version":"27.0.1","sha1":"d87be56ff8610b0220bc957fec8b6c8a5de057d9"},{"version":"27.0.2","sha1":"e94b096ee19fc5963b4e9a9cef42155fd3b2d74a"},{"version":"27.1.0","sha1":"74588130484eb3ed10e82026ecf4a2bde80618e"},{"version":"27.1.1","sha1":"e731e0851208679ad7bd0c00e40ab44e4d18eac7"},{"version":"28.0.0-alpha1","sha1":"1623654e634468a5f26fbb143b6b259c89147c9d"},{"version":"28.0.0-alpha3","sha1":"e219bc8f078d8a75351879ac97a4a3f7efbdaa4"},{"version":"28.0.0-beta01","sha1":"43477ee1ddbb612e2bd3e3419a1b8c492577c27"},{"version":"28.0.0-rc01","sha1":"1d1bea8e6f946dd54529cc21c8400993627ed305"},{"version":"28.0.0-rc02","sha1":"8c9a40f5ebb9b17e4383da37f779cb53f99bb371"},{"version":"28.0.0","sha1":"619978205950624a6139430eb70fdd0664704efd"}]},{"package":"support-fragment","versions":[{"version":"24.2.0","sha1":"a0d92cf8eedde2315aef4c6af6b7238690d9b461"},{"version":"24.2.1","sha1":"e48a682dfeddff2d17db4049b8780562d8d7f7d7"},{"version":"25.0.0","sha1":"e53a68c2eddd1f8f67329295d523acf98cf566a4"},{"version":"25.0.1","sha1":"2207e9dfbd559663021a8c77a489fe93871809b"},{"version":"25.1.0","sha1":"b81edf47bb0c1375f5de790b261ceac0348de052"},{"version":"25.1.1","sha1":"a07608dbe64f77e020a97aa66a2363c6de3e4e5"},{"version":"25.2.0","sha1":"bee35119590f1285d0b30f67041973b41debd5f2"},{"version":"25.3.0","sha1":"5c14290594160bf5ead222f470b4c4dbb2debea8"},{"version":"25.3.1","sha1":"9efba7de2e5c0ab4837b2b8ae11f9e5362bb3dc8"},{"version":"25.4.0","sha1":"495914bad5409150a36c3c0c90656e30342e1819"},{"version":"26.0.0-alpha1","sha1":"3aa87c168d485de76411c0bdd47d3924fb291c6"},{"version":"26.0.0-beta1","sha1":"bae5bf8b927ed098b133c45c8e7f53fe06c22564"},{"version":"26.0.0-beta2","sha1":"a367254337654744d5786cdb04645e09630cb1d3"},{"version":"26.0.0","sha1":"3c9d8ddee1d21874abb6f509c7bda8df142af59d"},{"version":"26.0.1","sha1":"7baa43114dc4aaf57498b239a7411fdbf51d594"},{"version":"26.0.2","sha1":"26cf0203b3dbb19862c462b7a711b759580a3393"},{"version":"26.1.0","sha1":"ddacf369bb98dd6558606558de8ddcd53895cf91"},{"version":"27.0.0","sha1":"59dd9a44739fcfabf65f0d6c85333562a1646952"},{"version":"27.0.1","sha1":"6bdb260a2a656c2ed593dcf841f1518890ee4b7a"},{"version":"27.0.2","sha1":"70158e624841a83ee3c007101014af93087b81b0"},{"version":"27.1.0","sha1":"2db3ef0a4733b8e65977621e161d1529c0020751"},{"version":"27.1.1","sha1":"eb8053ebf038b7561c29e5e8964664fac05f375e"},{"version":"28.0.0-alpha1","sha1":"1038de34b4bdc2e39d4b73fc4777f1368c0c1af6"},{"version":"28.0.0-alpha3","sha1":"bfc2659fb46cd45d0d312c300ecd037c90e80e18"},{"version":"28.0.0-beta01","sha1":"4dc3eecbdb88bcb7d24fedfee526ca66dcdc6e96"},{"version":"28.0.0-rc01","sha1":"10689349f771e550dde4d6c7de4f5543cdf4f0af"},{"version":"28.0.0-rc02","sha1":"ddd2d04e216aabc4c9342d172febfa365b171954"},{"version":"28.0.0","sha1":"f21c8a8700b30dc57cb6277ae3c4c168a94a4e81"}]},{"package":"design","versions":[{"version":"22.2.0","sha1":"fcbb3fe3e83ca7d1b0de583d8696cb6225de3e7d"},{"version":"22.2.1","sha1":"8422b5e7c81667c58f289a5c043ea5693cd45506"},{"version":"23.0.0","sha1":"c45a63008e5d23e7bc9d48bf5ebd9c90e6e704c9"},{"version":"23.0.1","sha1":"d8538812c5119cff7435eaa95362b44ddec5f1b"},{"version":"23.1.0","sha1":"88a6c3ea503e2740c2091e8d5112d383a21f05fe"},{"version":"23.1.1","sha1":"2df95c4d5dbe56f01cca3d4fa0656637105e22eb"},{"version":"23.2.0","sha1":"fb77200e74f5aaf27584ee42b5b4207a22454c3c"},{"version":"23.2.1","sha1":"4c6c00c1502c770abe7e29bb74eb1e424fd7b606"},{"version":"23.3.0","sha1":"500aebb33c80b1119e92b6594ddb28195a0c415"},{"version":"23.4.0","sha1":"3fffd17e6488132ee8edd4619eaf0644138da911"},{"version":"24.0.0-alpha1","sha1":"bedf9add1e13552ada406b11c05a10c81e3288c2"},{"version":"24.0.0-alpha2","sha1":"c42490695fef0d485640fe0c65a467a5adc5b472"},{"version":"24.0.0-beta1","sha1":"1a3bf6b89e7e9584de2b2f33512e09600af8c6a7"},{"version":"24.0.0","sha1":"cae49b48dd595e2dc264470dab3c89ccda1027c1"},{"version":"24.1.0","sha1":"29a0c8bc3484ef7febceb2e3ed87966244fbefd0"},{"version":"24.1.1","sha1":"74457a2f88962d0f0f7cceb87e87bca386ca4559"},{"version":"24.2.0","sha1":"1e789836cbe4344f369b059e21465efe8eedc39a"},{"version":"24.2.1","sha1":"7a97410df1dce8cfa9bad450b7894e63d8695c4e"},{"version":"25.0.0","sha1":"76f7f3ad694361cc277e8b2f2f7162ac90554c02"},{"version":"25.0.1","sha1":"74c22537a618f5171e7fdabaa1586dcd2c8a5d41"},{"version":"25.1.0","sha1":"5ed88b57677f3d9b8b20f5104ba14454e36e3ac9"},{"version":"25.1.1","sha1":"3ac3da7cf086fdbc5265ccc27deb2b9676bb6216"},{"version":"25.2.0","sha1":"10832afe43b621a6c53af39c7c9b353867305ea"},{"version":"25.3.0","sha1":"d73532719226a8e15aa8a93aab4b94265d9562f7"},{"version":"25.3.1","sha1":"c8ade2ea66a0bb5213c7fb87a36e0df6d528586d"},{"version":"25.4.0","sha1":"5d53141f4e826a9086af4cacd6860101812530fe"},{"version":"26.0.0-alpha1","sha1":"ba41c2ac43fde8e3f5ca8a74a5f12f024ecf3f80"},{"version":"26.0.0-beta1","sha1":"58932afb63dee72f553fcdcad2c65ee63156b74f"},{"version":"26.0.0-beta2","sha1":"8a55575b3927a32dcb47e358fc0f355922ceb4b2"},{"version":"26.0.0","sha1":"3e5dc0994cac3a01d939c60f2d5f863670a70e41"},{"version":"26.0.1","sha1":"f253be4ce2a3b3c88dfa60db118a0d35d0dda3c8"},{"version":"26.0.2","sha1":"bcdc4dfbf2031e439fa349c3da5bacfb467f5bdc"},{"version":"26.1.0","sha1":"d1ed8196a127cf70aa3cf5b12200f6b182e18a7c"},{"version":"27.0.0","sha1":"abd6caba8e09c9e4e78625a257f55156ecad5c7a"},{"version":"27.0.1","sha1":"c47f0df6cb364560b145304d2a59e3d6a773f0ad"},{"version":"27.0.2","sha1":"1addb51d289eda7d910ad3fb6d213a06fe257f01"},{"version":"27.1.0","sha1":"99807e82801d15e64d789ce1cbb6222034005cf5"},{"version":"27.1.1","sha1":"fc171921ed69e81607fbe7e8dd041f0e0d5d0395"},{"version":"28.0.0-alpha1","sha1":"7c3c4963bf6b583e1e13530b57e7d140d59e2521"},{"version":"28.0.0-alpha3","sha1":"7aec421c8380b8e01d33876a295547c543af693"},{"version":"28.0.0-beta01","sha1":"f0c8c2ceb5f60247e254174ea6183c298a86b652"},{"version":"28.0.0-rc01","sha1":"bef882d1796b085fa89a5f1cec42a8edd2351e6c"},{"version":"28.0.0-rc02","sha1":"bef882d1796b085fa89a5f1cec42a8edd2351e6c"},{"version":"28.0.0","sha1":"bef882d1796b085fa89a5f1cec42a8edd2351e6c"}]},{"package":"transition","versions":[{"version":"24.2.0","sha1":"846a1560d45a002c3d7083a92f032701f11b4b94"},{"version":"24.2.1","sha1":"3a3db51063a4571ba340f850328eba1b43bd6e3b"},{"version":"25.0.0","sha1":"8a41f15ec710ce5838730be97ac85e0745694993"},{"version":"25.0.1","sha1":"622615b516d2118e7780d98a6daa3b12596d1776"},{"version":"25.1.0","sha1":"618b424115c8701b2fa145769b066bf9f7608969"},{"version":"25.1.1","sha1":"fa584aab1d32242274ec19a495327f31c9740edb"},{"version":"25.2.0","sha1":"e24515ea74b5f6b4a3c2794f153bf0db6df7fb96"},{"version":"25.3.0","sha1":"48ae7e23f3ea77b625e1f1740dc234d3f3e572d6"},{"version":"25.3.1","sha1":"63c8aeff07e0da3c2c734bb1aef45ae39aa74286"},{"version":"25.4.0","sha1":"f036f887324d88be1f9e2fc34af86f865d14fc18"},{"version":"26.0.0-alpha1","sha1":"6d155f5cfafc77591ba5ee721f1b61ec3cdb9fe4"},{"version":"26.0.0-beta1","sha1":"1636cfa9ddf5d4f6bc295beee8c09339a1b69868"},{"version":"26.0.0-beta2","sha1":"c3984e9695921a72537d9a7f90ecd638d97e0ad2"},{"version":"26.0.0","sha1":"55166ae05b1b1482f9b86217023651d457799c37"},{"version":"26.0.1","sha1":"740a90e235cecd6893449f2768447796476b7b2f"},{"version":"26.0.2","sha1":"8f33c438318d4b8526fc802a882d9a23520bde6c"},{"version":"26.1.0","sha1":"f60cfea08f75a3a8dfc5a4d7ce7dbdf0e20c3055"},{"version":"27.0.0","sha1":"cd83e44ae4692331dca9a04f2ebd147fd5c34d35"},{"version":"27.0.1","sha1":"a1a632c4f4e3858b0644d58daecdba14cb9dc5b1"},{"version":"27.0.2","sha1":"d546cfbfa337c4655d8c5a77d74a49f32619cc5b"},{"version":"27.1.0","sha1":"315fe238d8305be5745041ccc1fc40c908660404"},{"version":"27.1.1","sha1":"1fe32022d8f46bedbed429be3fa5277b4b429d96"},{"version":"28.0.0-alpha1","sha1":"da40605ce8ca5c0cd006f43f9d29fc71e149c2d9"},{"version":"28.0.0-alpha3","sha1":"2c4b27d0c33c32d168335b40806a4acc5a918f6b"},{"version":"28.0.0-beta01","sha1":"8e1e38b3a9f36169cb1dd18f600aef893e575f09"},{"version":"28.0.0-rc01","sha1":"6deb24d1a9b72e2af01e55f3f370afc2532f42d1"},{"version":"28.0.0-rc02","sha1":"8b406f2a90801065bebb7e435162749aec7f0790"},{"version":"28.0.0","sha1":"3ed7903f03d9210ccd923d2e2412a3317e3a37e7"}]},{"package":"customtabs","versions":[{"version":"23.0.0","sha1":"3d6db872d9fc8419628433af88cc08a3c4bdce7d"},{"version":"23.0.1","sha1":"be3f8835d61c92d31791882dc5b43f9c8cf38b7e"},{"version":"23.1.0","sha1":"b20934f748afadb5f7a6101b3ca0e996ac002e1f"},{"version":"23.1.1","sha1":"d512551982d5d27f849dcd85150177f3b6bde2e1"},{"version":"23.2.0","sha1":"e180e5c3af0cf798e00b9e4012b1489ed3b53acf"},{"version":"23.2.1","sha1":"1b3dddf9fbb2a415d8541273ce0273463ac72867"},{"version":"23.3.0","sha1":"c90ab4e90f928794a7983179a57c3eb2b2609f52"},{"version":"23.4.0","sha1":"24a37bb3e858b15e19cbf802002e07868c93f431"},{"version":"24.0.0-alpha1","sha1":"26c9d87835881697842d8e0d4708737a863f104a"},{"version":"24.0.0-alpha2","sha1":"f088b78603d3f3ac8f4cc32030f5af298c952f49"},{"version":"24.0.0-beta1","sha1":"6d9e20a2e2d3ca016485428a558c4c59a163f0af"},{"version":"24.0.0","sha1":"87c1f29e7d49222bc7748a3947a5a31833e29852"},{"version":"24.1.0","sha1":"438f65ef579574efb1f184f15c0231231c4e5359"},{"version":"24.1.1","sha1":"faf34228cc014c66664a23b0a327868858253f17"},{"version":"24.2.0","sha1":"5030d04db113a197a302ce8d2f94f6243cd93053"},{"version":"24.2.1","sha1":"f277bcb243293c356c4d3a7dd5cbedb473cd2fb3"},{"version":"25.0.0","sha1":"cb124731f3c0010fef79dda3bf2cb9c289503f88"},{"version":"25.0.1","sha1":"a86555933d3891667947d729c9904979ce522926"},{"version":"25.1.0","sha1":"82f10d152867229d2e58d09ddded1359dcdc3396"},{"version":"25.1.1","sha1":"2ec6cbef4989053d7ed7a5086125c08eb6aca379"},{"version":"25.2.0","sha1":"37a6632e77f3ed732e8ea7091077e1b935fadfc1"},{"version":"25.3.0","sha1":"d0eee14596865f0e94bbf51894c8fdb5cdd0c689"},{"version":"25.3.1","sha1":"230bcc1dc9e28ea88dbec9adc36ad271e84f59dc"},{"version":"25.4.0","sha1":"56086ba3dd98215b8fd8412806a573a1cdf7faf0"},{"version":"26.0.0-alpha1","sha1":"e89bf22745f1eb382885aa5bba9dd9ef47c75d55"},{"version":"26.0.0-beta1","sha1":"6fa5489b9efc861da8c53c7473fb3a5b898aaa76"},{"version":"26.0.0-beta2","sha1":"877b6735702fc2e4f9aae7ee58b3dc5f29d9d871"},{"version":"26.0.0","sha1":"7588be9c1554b1294770002e4f5c8c83e51f33da"},{"version":"26.0.1","sha1":"7ffaa979c95b23919a5e9c5302423ffd6822885f"},{"version":"26.0.2","sha1":"fec03ceee68dc60885559924852d0da763ec7522"},{"version":"26.1.0","sha1":"85daf61dcbadfb5f6cac1c00a7084ddaa84d518d"},{"version":"27.0.0","sha1":"a0a9a4a8d20bcc1a6023fe96f53f584ced6f2417"},{"version":"27.0.1","sha1":"dee92be758b277c3d7126a05f7073390fc0f96f4"},{"version":"27.0.2","sha1":"1fca15c3776da5743cd2372087e17d40a360697a"},{"version":"27.1.0","sha1":"2a723201f19e070dc374e4d2b5fd59b7a4bffff4"},{"version":"27.1.1","sha1":"f26576e3f16f4374b2defa6388e1a0b5b615f841"},{"version":"28.0.0-alpha1","sha1":"771943753836c6ed5ad0bf3927b24a08bf739337"},{"version":"28.0.0-alpha3","sha1":"c887133d496ed45acb5af2c02d5d65fe989e4aa3"},{"version":"28.0.0-beta01","sha1":"986b62412f18a6137b4ee797fd2379d2c729487b"},{"version":"28.0.0-rc01","sha1":"e0d1a85a34e3a54ef14c188c76d571564b23c96a"},{"version":"28.0.0-rc02","sha1":"66a8a06c252995fa37d45fd9ba163b8129c43db7"},{"version":"28.0.0","sha1":"c9c33c31157ed35e89666ec324ebf83c944674a8"}]},{"package":"support-core-ui","versions":[{"version":"24.2.0","sha1":"d86b936e8785254b2dd0bd75216bd3093b547a87"},{"version":"24.2.1","sha1":"72cb44b1609ba8f853eddb9b761fbe8846cb947e"},{"version":"25.0.0","sha1":"ca492522c893d16408e7a0ea79f590a3ee634dcf"},{"version":"25.0.1","sha1":"d2dde3e938d0a7fad74fa103c48a25c451218e61"},{"version":"25.1.0","sha1":"7acd9e9562e252fb2a10aef710d15edc8ca3188a"},{"version":"25.1.1","sha1":"8a5ca276a6ca2267bc90b426d154f97b75871996"},{"version":"25.2.0","sha1":"cf763201f9c8ad8d4865932580dc9d3666611565"},{"version":"25.3.0","sha1":"fa5653856e94e37e2eaa889e3519390f55eb307e"},{"version":"25.3.1","sha1":"c05699af8e8a46ed42c09a72c70b61493d7c47c7"},{"version":"25.4.0","sha1":"e7c580a5dc7bd8458d789f40a58c568192bc46a7"},{"version":"26.0.0-alpha1","sha1":"b355a26833db04c727e6de5eac5431e0772aa4a8"},{"version":"26.0.0-beta1","sha1":"fca894e389df05edd0e27260ee206390fe3d433a"},{"version":"26.0.0-beta2","sha1":"4c4a1d0250af262443ded3f85640448acbb1562f"},{"version":"26.0.0","sha1":"ac9942e35aef3d5b982a2eb195aebaccce81893d"},{"version":"26.0.1","sha1":"d4fd5c88cf472af8ad08eb73ddd98b0dd0ad1087"},{"version":"26.0.2","sha1":"c55b19b6a499c4a78227ebc8390475d594e08d0"},{"version":"26.1.0","sha1":"e306308d54052a1ded7bf9c2f5c2fdf5152a1f22"},{"version":"27.0.0","sha1":"e5423fed0c47054eff7badc295aa36bace51d004"},{"version":"27.0.1","sha1":"5b078bf46dc9a209977ecbc805e26378c70da96d"},{"version":"27.0.2","sha1":"a8ed74915d7969d7c6b8c2caa7f958cb29b7f7ea"},{"version":"27.1.0","sha1":"54b6f9853ff5e53174622f3f6f7eb208aacebd56"},{"version":"27.1.1","sha1":"f9acdb8a4c3a9fe883fd7fa5efd3f0426bb9dcda"},{"version":"28.0.0-alpha1","sha1":"d45e4ff0e65440aab894fe55ae0627ebfb22bbb"},{"version":"28.0.0-alpha3","sha1":"fbdc7d639efb01e3765c37f9adc25b4730ed4e12"},{"version":"28.0.0-beta01","sha1":"45064a26f0b474c3e03ad69395f7cd58520f302d"},{"version":"28.0.0-rc01","sha1":"1694eeee5de0423d9aa2688eb5b9b0cd7a8674ed"},{"version":"28.0.0-rc02","sha1":"caa11099a5608018d389b719da31226f1eec8cb2"},{"version":"28.0.0","sha1":"96035b1030d7c3a81903966c2fa52117d36aa5b7"}]},{"package":"gridlayout-v7","versions":[{"version":"13.0.0","sha1":"934ddde0c74d7be9ba072a15ce72add905f45b9a"},{"version":"18.0.0","sha1":"ee013621d926f21f4a7537d93cb0c3ed4dae7e68"},{"version":"19.0.0","sha1":"89adac902539f9443eecb0a1e91ae44be0432390"},{"version":"19.0.1","sha1":"1bb686311df2e0d33e0cb5cfd9a2b6ec39e96719"},{"version":"19.1.0","sha1":"c823c7305afdf586d00269ff843c833204c6674c"},{"version":"20.0.0","sha1":"e7ad827e15cef174920926421c0ef8c29afda078"},{"version":"21.0.0","sha1":"3447057e081dd67f8946e6819b2ef55b17e2730d"},{"version":"21.0.2","sha1":"2173aebe1774662220a87bbcdf09574e2e15f747"},{"version":"21.0.3","sha1":"53bb2aa6c5347633ca940ed052d45a3d580af7e6"},{"version":"22.0.0","sha1":"25c5189f60f2c9b4616bbfe7ac7c202ac0f714a6"},{"version":"22.1.0","sha1":"d29eb6f612f9e270cfe99f2bc25e0c1a2a5993f4"},{"version":"22.1.1","sha1":"5cfc9c2ff71c7abaa174ecbe8093253d83d58a9"},{"version":"22.2.0","sha1":"56945a454d486c3601ffa2431b0cd8dd3f85a3c0"},{"version":"22.2.1","sha1":"c7b464111b5bcf7d66d2eea204178b798ff88a2d"},{"version":"23.0.0","sha1":"10d5cd7326b53c59d2b9792db3ea82aa70beae4b"},{"version":"23.0.1","sha1":"7d16e2e04789d44c7f6eddd143f36db4621b2d9"},{"version":"23.1.0","sha1":"438e72fb528e671bd243fc71632105aefec64f30"},{"version":"23.1.1","sha1":"5c7a33da32367788cf39913b6b0240ab43fd27a1"},{"version":"23.2.0","sha1":"cf36d48782364026ad5215fc82d1e82468743868"},{"version":"23.2.1","sha1":"a297d01fdcc21f4b078b572d05992454b1bdfe10"},{"version":"23.3.0","sha1":"21798809c0f6d1c4d75e546c047150f7a4c9491b"},{"version":"23.4.0","sha1":"1b1c2ca4718ae9052d634cbb47260a1689b6c59a"},{"version":"24.0.0-alpha1","sha1":"daf50f4d977392a6e87d1387d276d96c9245ebf3"},{"version":"24.0.0-alpha2","sha1":"3fd84550045219e387e327021c76f75956fa528b"},{"version":"24.0.0-beta1","sha1":"8668f9af39346868063e7cffb885c5632fe8a33"},{"version":"24.0.0","sha1":"9eee60c45ab1393184cf4625faf912a2f550a46f"},{"version":"24.1.0","sha1":"5850d54c742a2f89c75eb811d2eed90731893aa5"},{"version":"24.1.1","sha1":"4cef75720e2d332dfe21d20670750ec3ff09835b"},{"version":"24.2.0","sha1":"a88999b988189bdb8d9ce8b1de4bbe8a0af03653"},{"version":"24.2.1","sha1":"1942696489be0c73ce5d1db0f1b237ef22d810f5"},{"version":"25.0.0","sha1":"1b8e5146be9a9b1ad4680b2582a5b31b1c2dc0cb"},{"version":"25.0.1","sha1":"1c9b03b88a7d0e81c0a860e3107f79260bb32f78"},{"version":"25.1.0","sha1":"4e042d53e532a71791318ebdec270c7309b1a09c"},{"version":"25.1.1","sha1":"efc98c637e58aa8980d124c5885b197b9aeeca56"},{"version":"25.2.0","sha1":"ccb70ad113ba415bc0e7d5c4afe453e6d18c0e1"},{"version":"25.3.0","sha1":"c18f542a9210ecd1c1b216c918e9f8ae16884fa3"},{"version":"25.3.1","sha1":"7ebeebf1900469dd0f85595b2fe61809dc0d4130"},{"version":"25.4.0","sha1":"5ecf80b1da14a2c5a6a31ea684ff878a17aa2f65"},{"version":"26.0.0-alpha1","sha1":"460655b695ca3412a4d624942d68d4776399522"},{"version":"26.0.0-beta1","sha1":"fb3ac6b5aea681ac6c76b254265f9aafc4bb821a"},{"version":"26.0.0-beta2","sha1":"a43b961c4847d16b9776f29e49797518d66839b4"},{"version":"26.0.0","sha1":"332fea5b2856c0d4ae21021ec2138e70e2a681f8"},{"version":"26.0.1","sha1":"195d5d1071b7911cb39cadce17a5a68611fabc6a"},{"version":"26.0.2","sha1":"81f61359f69d6c4558783b9ba639c6e4aa956faa"},{"version":"26.1.0","sha1":"8173ffe1ae1f6dec5d035e6509b068e9b7c0f2d0"},{"version":"27.0.0","sha1":"b2c919b88d58c86842261dbc355b9fe6708cbace"},{"version":"27.0.1","sha1":"af53b2d69124d5f647937bf2cc17696a98cb1ecd"},{"version":"27.0.2","sha1":"ebde2a1d7e6d74a587e4cf225dca967e19d3c70a"},{"version":"27.1.0","sha1":"6ac1df9e6fb24480c7983efe5157ad8b35187897"},{"version":"27.1.1","sha1":"7aa018d4afc18cb2524fb44a0e7dc6e3a2c44f7f"},{"version":"28.0.0-alpha1","sha1":"1ea769540f2f232fca3bc87e6037605bc24494ea"},{"version":"28.0.0-alpha3","sha1":"42259a511a6ff2a01ae4395892e350a96ab26771"},{"version":"28.0.0-beta01","sha1":"235a62600ebae167f1dbf64c51b644e72e46aa32"},{"version":"28.0.0-rc01","sha1":"889188a518b47927a6ac595c6eeee28b96dba663"},{"version":"28.0.0-rc02","sha1":"48668db9b3bfc900394b377e5439d01442087be5"},{"version":"28.0.0","sha1":"c674d0600d8dfb34f5a020e0d83a7b06a8ad10b2"}]},{"package":"animated-vector-drawable","versions":[{"version":"23.2.0","sha1":"a853da427e6a8c096a5b049f107ade606c6765ce"},{"version":"23.2.1","sha1":"b15f35de6f54d627584013630925af41c5746e70"},{"version":"23.3.0","sha1":"76184e194ceb670eb6badd48d9c61bc5d297ef9b"},{"version":"23.4.0","sha1":"d9e3824976662f113e6c202091e5f4a503ff2b7d"},{"version":"24.0.0-alpha1","sha1":"6aad21afbd896a322b5118b3b4657bd65b13ffe0"},{"version":"24.0.0-alpha2","sha1":"1251d58d2695f186b02e45d75c70451068d81922"},{"version":"24.0.0-beta1","sha1":"fa77251b03c906810dbe021802eb9fcd314de03f"},{"version":"24.0.0","sha1":"9fe84792a065924aebf9c7f3c5ef44d34d6a2f75"},{"version":"24.1.0","sha1":"c0a01fd9a7272e306e95fe3ac0eb27b41ec6c3b3"},{"version":"24.1.1","sha1":"c845ec3f9011c89f7159d35345810788d746c8db"},{"version":"24.2.0","sha1":"8b0e4a6e6c4d88cbcef19de7c03f3d04e6c7bd56"},{"version":"24.2.1","sha1":"91ca30951371a61b4c4c886cae602a9a67f6d43d"},{"version":"25.0.0","sha1":"ab55eb97cd260cb4423795b32163d21c7c83c5d5"},{"version":"25.0.1","sha1":"3a6accb53e2317ffd7df7bc312c12c450e7bc3c9"},{"version":"25.1.0","sha1":"cd287c6d8e971de28be59da25e298885721acd34"},{"version":"25.1.1","sha1":"313cb5e10120e2d1dee88ec168e938c1bdf7e29f"},{"version":"25.2.0","sha1":"97766319d0330dbed33ae1de8827eff9dfae6d3d"},{"version":"25.3.0","sha1":"20d65ac59e9610d7ff518975161b965b39cdd64a"},{"version":"25.3.1","sha1":"4619ee1b1f48241c6be79f3eff43f536c75de40b"},{"version":"25.4.0","sha1":"1345b7948c0c1c68c2032f63cd5377f3e1ae16e4"},{"version":"26.0.0-alpha1","sha1":"fea8eb1e5b250e9c5c0b863f7b86b5154f57912d"},{"version":"26.0.0-beta1","sha1":"2791da6709140f0d6c96bd86f9383f9ce08e60b2"},{"version":"26.0.0-beta2","sha1":"6dabb6ebf23a19842ed3774193ef4b3157ad73ba"},{"version":"26.0.0","sha1":"f7ad3d5eab126bede46a1da6fd8af6173fb261aa"},{"version":"26.0.1","sha1":"ed6e6807b680efb1f722b3874f15dbb2f7decd61"},{"version":"26.0.2","sha1":"b9b56e503d08c4246a30f281e3fe8511c6cfb430"},{"version":"26.1.0","sha1":"cf6041548f9b5d71dd068d58e7dfd2fbaec2ff64"},{"version":"27.0.0","sha1":"b0b4c7e2cce2e3f6596f43846a69d8bf8465b611"},{"version":"27.0.1","sha1":"9da101c8b10d3f54997b3af65943d4f3256da79"},{"version":"27.0.2","sha1":"413f28f78220e51085bdebc618137ef8b60a1abd"},{"version":"27.1.0","sha1":"de8e39a293c7ae0bdb75306d60b40aa1a4e88d05"},{"version":"27.1.1","sha1":"88c2fb880a3e8c902b8f247197509d489e8a3501"},{"version":"28.0.0-alpha1","sha1":"2f505ea023eaea6dcd382dfec6c00a6f7fe1c732"},{"version":"28.0.0-alpha3","sha1":"7d39b147adac98f1b220a68a6d3383174200656e"},{"version":"28.0.0-beta01","sha1":"65f10248bb5a6cb98e8ca3cbce452781baefbc51"},{"version":"28.0.0-rc01","sha1":"4280b0d42166e88aa89954cf00c64088eb141106"},{"version":"28.0.0-rc02","sha1":"bf2109d5e03fa2c6dd7cbaed0782cbdb8f9529e3"},{"version":"28.0.0","sha1":"e2d41c2a032145313f42ab38a2d9757c5d1ebc19"}]},{"package":"support-core-utils","versions":[{"version":"24.2.0","sha1":"221d118dd4764d3bb968ac463e52f5d6627dd663"},{"version":"24.2.1","sha1":"1f6a640b751965302755ce55930542116a36b67e"},{"version":"25.0.0","sha1":"2bb0315e265c2a6e4d25671369e7b3429a3397d"},{"version":"25.0.1","sha1":"d1a435ce272d5d899ab200194f53aa11895f574b"},{"version":"25.1.0","sha1":"667f5aa08220f0b78a329a55dc364691d872cdc0"},{"version":"25.1.1","sha1":"21206d3c39b137b522c64fadab90420ae7d4a6f9"},{"version":"25.2.0","sha1":"1ca5cc84ed7404af3a1fed4678a69195e41b9bde"},{"version":"25.3.0","sha1":"30a57915f8c7ea3cbfc28eff4628bf467776069e"},{"version":"25.3.1","sha1":"e4e6f72f7921c612e2397008e5a14ff00c66a374"},{"version":"25.4.0","sha1":"edacc8a2cab01060531f22f7f069724b718d9a0b"},{"version":"26.0.0-alpha1","sha1":"26151f6fe9157e43507533cf12fabc66c6a99c56"},{"version":"26.0.0-beta1","sha1":"580f04907b156d3e2124996cb247d686a07ca7f9"},{"version":"26.0.0-beta2","sha1":"29c0fcdb6a3b7f505654d5f39a083f8a7fbba47c"},{"version":"26.0.0","sha1":"bb0c0d385c84eba94ea7052533c2af2c5c00dfa8"},{"version":"26.0.1","sha1":"bb3eafc11beff93da2c3dfe3b3f452c3daf9515d"},{"version":"26.0.2","sha1":"641bb56a309f50fe37ca6c5e48e7665f739e7c19"},{"version":"26.1.0","sha1":"1bfaae21c4d5c5532c5e732071e9ce234cd58cff"},{"version":"27.0.0","sha1":"29f7e04565e3dce20cdd6fc288f901031f32a509"},{"version":"27.0.1","sha1":"810bdd1d47d61e537bba0c1252f92e5f564b1edc"},{"version":"27.0.2","sha1":"69518e90755a25efd6ba34cea2c4c1cce92a0ccb"},{"version":"27.1.0","sha1":"1f21723126baf81f154326bb5f2e901cec82d03b"},{"version":"27.1.1","sha1":"b3a7a4040fda0c5138dbc8c477103b6723b9dbe6"},{"version":"28.0.0-alpha1","sha1":"40d854c949e2276759bf3e0a0f5a4be0ac348806"},{"version":"28.0.0-alpha3","sha1":"8442f59fcabc5e8ef986d9e3ab478388a90a8b1f"},{"version":"28.0.0-beta01","sha1":"e1c1e2e69ef575dd64ee3264968082764ddd75c0"},{"version":"28.0.0-rc01","sha1":"b604b354fa83a938b9407160fa32c4604060f088"},{"version":"28.0.0-rc02","sha1":"96634c1a6baf1f3abcdc48343f24c6a901ff942d"},{"version":"28.0.0","sha1":"29b1bb783f1a86eba7f1618bad58842bde72892a"}]},{"package":"support-v13","versions":[{"version":"13.0.0","sha1":"3cbe273d384ea56fb535c2d8405a9b64c21fcfb3"},{"version":"18.0.0","sha1":"6a2fa0c0b54359ee359fd40244e1ce2dc6cab408"},{"version":"19.0.0","sha1":"cc8ccb8ee435883471a5f5856b00f5f86d3f938c"},{"version":"19.0.1","sha1":"a4d00c1fa39c05a544fc2221af40bbf76e07c7df"},{"version":"19.1.0","sha1":"6b852a232f642c75a7fdfc7212b841b8c8f8959c"},{"version":"20.0.0","sha1":"de30c7dede13571678c907fdc989ba7028244f7"},{"version":"21.0.0","sha1":"147313395452ae50d960885531a01651acc92b9a"},{"version":"21.0.2","sha1":"51b40a49f465d17a5e886fdb18c8981570d5e739"},{"version":"21.0.3","sha1":"3ef5b4e969dbd1f91c258a344c32d059d5714afc"},{"version":"22.0.0","sha1":"aa5c64498c723c7ea1c7e6cbbaf2916937eab5c5"},{"version":"22.1.0","sha1":"4a7a2915e62c2ecf38d0168044c9bc0d6d901726"},{"version":"22.1.1","sha1":"98f360b06da8f32d4ef2008936e1423b873730f2"},{"version":"22.2.0","sha1":"49a947b60219cbcf897d30218a4631b3e39c15b9"},{"version":"22.2.1","sha1":"1e56c3edc3461029bfd60663e6e95860254e8a39"},{"version":"23.0.0","sha1":"50b6ea5b4160ddbcc7966d3f6837ecb85ecefd3b"},{"version":"23.0.1","sha1":"208b4644629446a334fae6a8c5fa7ade8b983ae6"},{"version":"23.1.0","sha1":"77e34b6545e8594b102bf97c50c57071f161f88f"},{"version":"23.1.1","sha1":"317a11af26372855f60630f2a9fa77275a96df4"},{"version":"23.2.0","sha1":"c26b3322c51d4bb86e159a1f60b33d06af470c4d"},{"version":"23.2.1","sha1":"a33e03a421677bc5eac43d4bee5549410f374854"},{"version":"23.3.0","sha1":"9fde36ec0799baaaccf9ebe535e532d4524f1725"},{"version":"23.4.0","sha1":"2a3ee12e390248b8dc7dfaad73731a033382a026"},{"version":"24.0.0-alpha1","sha1":"a6bdf9c4362f92536efc5c3df85db3de8b799893"},{"version":"24.0.0-alpha2","sha1":"6debada28c793db93eed57b4f7e92e3a90dcb476"},{"version":"24.0.0-beta1","sha1":"c48863101f50e2c57b6700fccf231de06d8a993d"},{"version":"24.0.0","sha1":"6c6aa163dd660529aff828098328c272ba8b63da"},{"version":"24.1.0","sha1":"4c65669c3b1fefc5d4cdd161f13d62ede2444830"},{"version":"24.1.1","sha1":"a353aeee594afad1afc2e62112554b120d31f33"},{"version":"24.2.0","sha1":"215ecbab13c49ddcee68018b7b23fda2149e91f3"},{"version":"24.2.1","sha1":"1674a9acf46a184719bd5954cd91f52a899e4150"},{"version":"25.0.0","sha1":"306421ddcae951150aac39400053dd3451566f2b"},{"version":"25.0.1","sha1":"655efaa6483758de411305fdcb7c59502009ba79"},{"version":"25.1.0","sha1":"b004684ca7d995f3c4286aeb2d748d501a3fcbf3"},{"version":"25.1.1","sha1":"fd72898dc755365895e10537e85c4487722472e5"},{"version":"25.2.0","sha1":"ca51cff0dcc395d463a493479f474fda1733074f"},{"version":"25.3.0","sha1":"5b69c49f1d0a5cc926eca846c50bcbe0a3cce6e1"},{"version":"25.3.1","sha1":"5db9d35a872d1cb798134c7863a7838279d6175a"},{"version":"25.4.0","sha1":"cecea74dd3a470c66e7deda71a1f479fefabe203"},{"version":"26.0.0-alpha1","sha1":"affd915464a6a628cec8e87a0b10cc0ffbc85e9c"},{"version":"26.0.0-beta1","sha1":"26098fc94f49464497e08ae7fbd39f459696e06b"},{"version":"26.0.0-beta2","sha1":"f8e2ac6cfc930227df71dcc1bf4d8ffae00bd051"},{"version":"26.0.0","sha1":"4a042ffa0b5ada40cb640d5e1a7ac9d730b57f95"},{"version":"26.0.1","sha1":"2973b260b1fb8a915835ddde9916c59360e4d832"},{"version":"26.0.2","sha1":"f6027b77852dc2779062c352f091cda6ad78b44e"},{"version":"26.1.0","sha1":"24d7998de9e88b9b6822d949dbc490e5132922b3"},{"version":"27.0.0","sha1":"fd0671d2642e490b2c91bfcb7ba5e52042a7bc54"},{"version":"27.0.1","sha1":"a1b3dd098180f932982c0433ea6f3af626455b91"},{"version":"27.0.2","sha1":"9325fd4d5631669482cc72ab50995eb86735e373"},{"version":"27.1.0","sha1":"8a685a09fdd4b9d8f21fe9f5c5b0ea2bd07e6d45"},{"version":"27.1.1","sha1":"c5bf22d2037f91b74e57368a16319d0c29161bf9"},{"version":"28.0.0-alpha1","sha1":"f7e86c2e97ef637d341838b57d7ec690f2e98075"},{"version":"28.0.0-alpha3","sha1":"b08b79a07a52bc74464dc8eafa69b249833b3eb"},{"version":"28.0.0-beta01","sha1":"dd9315ad686454a696c5b85e4fef61eb2925c3eb"},{"version":"28.0.0-rc01","sha1":"3dd77e04a1497767cf13f4a1bc90ea9d3274a10b"},{"version":"28.0.0-rc02","sha1":"46bd8c92d8402acdff29a48cd3bd4e586df244e9"},{"version":"28.0.0","sha1":"8c5912be1c61bd640ec96606a4959ee55d2c87fd"}]},{"package":"instantvideo","versions":[{"version":"26.0.0-alpha1","sha1":"913b8cb81c13aefe2a684a6e5eef84f110122241"}]},{"package":"support-v4","versions":[{"version":"13.0.0","sha1":"3fe75433da602500cc71fcc1c46e5550291dd45d"},{"version":"18.0.0","sha1":"44ec51e9d395a47aab135e5e1b556c0007442156"},{"version":"19.0.0","sha1":"afee690af14bd2c32d400cdf78e746fd45c386fa"},{"version":"19.0.1","sha1":"587da9a481ffd341d2fa091704489b89845ddacd"},{"version":"19.1.0","sha1":"85f201b380937e61a9dce6ca90ccf6872abbfb67"},{"version":"20.0.0","sha1":"fc3a5a6c9f7c757a66dc067eeb2ac3d86b4709e9"},{"version":"21.0.0","sha1":"30415aa4bc4087c8b4e2b59c7310cb146db8e83f"},{"version":"21.0.2","sha1":"39eadabc45185963eaec97aae33466fb5a40e9a"},{"version":"21.0.3","sha1":"29e226320a680ca8257c7cfcb35f252bd5ad231b"},{"version":"22.0.0","sha1":"1993ac462451840cb8cfb62d59800aaf8ae60587"},{"version":"22.1.0","sha1":"ee71a22b55a68274c231d6d5b4b8ba06f4dfa4a2"},{"version":"22.1.1","sha1":"98b98aff05daf0dc0fc8f7470704accc56375bf7"},{"version":"22.2.0","sha1":"bd75a3bbbee282d9a95731734056b73e361594b5"},{"version":"22.2.1","sha1":"465095e37d01b4a74205eb2452cc0bf06fcec50c"},{"version":"23.0.0","sha1":"480436be323f0ccfb34bbd3353739bf130638104"},{"version":"23.0.1","sha1":"9e8da0e4ecf9f63258c7fbd273889252cba2d0c3"},{"version":"23.1.0","sha1":"8820cb60b2eb5c707be237476fef1b78aa3ccdbe"},{"version":"23.1.1","sha1":"353820becdd80e136db280bd76725e67a7ddc053"},{"version":"23.2.0","sha1":"a15ad9aa0397e709ceb7c44e56d62999a1c6aa42"},{"version":"23.2.1","sha1":"7828f10a2e902c52f63d7188aad14c163117f87a"},{"version":"23.3.0","sha1":"49873601601be82252ec13851dafcc24a9fdc310"},{"version":"23.4.0","sha1":"7a802deefef9561d90a440994c3e6eed81f2c241"},{"version":"24.0.0-alpha1","sha1":"ff97a673e1dadaf125e2d41d31b67155acece07a"},{"version":"24.0.0-alpha2","sha1":"56bc72100683f7e0f9d4516ffef702589b6c95ff"},{"version":"24.0.0-beta1","sha1":"76d02ae99a912751e10445ffe4f2791c7dc485d9"},{"version":"24.0.0","sha1":"93dbc95082794e79d1968e6f843833d72cdf7454"},{"version":"24.1.0","sha1":"eee1ac265500bb4c5d7bfd2b462508935d127efb"},{"version":"24.1.1","sha1":"57c006a017aa2cbe3d32511e5ac28ee76cf9f51b"},{"version":"24.2.0","sha1":"d0bf6fe10133999e6a9713ec9cd1fef6c068d6fd"},{"version":"24.2.1","sha1":"412852310008082b9a5f3c444ab15632d7f9eaed"},{"version":"25.0.0","sha1":"53a9912dcb1c897acb889decf4edc359ad593666"},{"version":"25.0.1","sha1":"5b2fc78bc28712cd257cec8be5c27c257724881e"},{"version":"25.1.0","sha1":"a14c1c087a1a1054b289ffd6bf2807f5d513e47c"},{"version":"25.1.1","sha1":"28c2841a27008ae1b29721f7930b232a7666d391"},{"version":"25.2.0","sha1":"3b9231d6e95d9b7286332e2fc9bab15fe47b14a5"},{"version":"25.3.0","sha1":"e10def704cbe1ba620c8ce0b1b221b68e7565df3"},{"version":"25.3.1","sha1":"e8f2835adcd883b3778f34121368892a5d1c188e"},{"version":"25.4.0","sha1":"810a8b1b9b6382084ed07828a55ece847d3ca5d8"},{"version":"26.0.0-alpha1","sha1":"f4537fd3f28cafe83ef65dc9085ca9b8a5305b9b"},{"version":"26.0.0-beta1","sha1":"20927a4ea03e36807d359da2b0ae7ecdd0f312c7"},{"version":"26.0.0-beta2","sha1":"5b824e87c668087f8ac37b178e7af76e7696656a"},{"version":"26.0.0","sha1":"82b63d0d427961d4fff29794fc2e29d7cec934ee"},{"version":"26.0.1","sha1":"4f8a92fc2d927d35f0451e92d7c14645017709d8"},{"version":"26.0.2","sha1":"762902b1aa5eb48bdb1a1f84b1be19a7b1285708"},{"version":"26.1.0","sha1":"444114b772e5eee3e66f9236ace4acc1964a33b8"},{"version":"27.0.0","sha1":"8685885a013e73be3d226bd0a5ea4f8037089766"},{"version":"27.0.1","sha1":"dd6e64129a8fa3e09abfd2ad0c006e78dd69a50b"},{"version":"27.0.2","sha1":"9150df5ab9738deef9395cbcdcab7751865849a1"},{"version":"27.1.0","sha1":"7f00bd779a0998074bbb2137e382a2fe2fc14458"},{"version":"27.1.1","sha1":"b72551b47d1abe28471407e7ff7f8960bd8eeca1"},{"version":"28.0.0-alpha1","sha1":"d44f3afc63d29c91fc64dcc0012d877bc7d0c4b1"},{"version":"28.0.0-alpha3","sha1":"8ca9a57945528d850ac0c490800a6f4bcf65fb6d"},{"version":"28.0.0-beta01","sha1":"8844e159c59df791a8924cadb797b308803eeb69"},{"version":"28.0.0-rc01","sha1":"6afd997ca17339f3fbcf1272930bb0a510c79c1e"},{"version":"28.0.0-rc02","sha1":"da435bf3d4341e3f5bea104a2d4706b5702ee26b"},{"version":"28.0.0","sha1":"32a50cdcd418e69d5adf85affc533dcc88660da7"}]},{"package":"support-emoji","versions":[{"version":"26.0.0-beta1","sha1":"c2323cc9d6eb955a44b9b3d8fd5046f14e6a19a7"},{"version":"26.0.0-beta2","sha1":"f985077a531cd6b411eaa8f427b5255387517411"},{"version":"26.0.0","sha1":"93ba7d8d971153930be33c91767058a12d73ce20"},{"version":"26.0.1","sha1":"4d495cde2c8dce084d66315ebce2cf2abc9dfb04"},{"version":"26.0.2","sha1":"924fe843aa34f82bfdf3b389ea239d7cac6359ef"},{"version":"26.1.0","sha1":"d656f32fc3709a9bdef9680af56bd20238953a0b"},{"version":"27.0.0","sha1":"5ae14d842119d73a47d6e8d202b3e91c9d7ed202"},{"version":"27.0.1","sha1":"9bedd6de578e0de1a4f0623939ce41316234d7df"},{"version":"27.0.2","sha1":"cdab078cce096fbd089c9a68b94ff1aa3cc4241d"},{"version":"27.1.0","sha1":"5879b5650f25bb7c62859731040419996d7266ab"},{"version":"27.1.1","sha1":"397311a7a58294bebb6c37be26f7b0b305aaa780"},{"version":"28.0.0-alpha1","sha1":"597e8f02ad7966fa18b36c601f1d119a470e8e5d"},{"version":"28.0.0-alpha3","sha1":"4d663a856186c0293f7d11c8a76e82f58b05ddf3"},{"version":"28.0.0-beta01","sha1":"22d2c3e424d7fc5bef133484af841d51f1fa8c51"},{"version":"28.0.0-rc01","sha1":"6cef9fca261c1d50dc4c1a401d266e69d7955249"},{"version":"28.0.0-rc02","sha1":"be6b3bc83af2bb8ed2980639e46ce961f92b29ac"},{"version":"28.0.0","sha1":"8520f1f219cd4c88db67403a8b7ad0025faec6a6"}]},{"package":"wear","versions":[{"version":"26.0.0-beta1","sha1":"28410c866909fa57cd8bc6094bf0d3584798d57e"},{"version":"26.0.0-beta2","sha1":"d60925cc75c5d410441afa20f302d6484c533bb2"},{"version":"26.0.0","sha1":"e1b008938265d1da669e987e3ce23f639ab498d4"},{"version":"26.0.1","sha1":"de1fbfa08ad0486d985dcacbe4ad842d2bde4399"},{"version":"26.0.2","sha1":"ad6e5ae0d8e3ea832730771188cbdbe0ce37bebb"},{"version":"26.1.0","sha1":"bc28512452adaddfe52fab6390169e69a735a53c"},{"version":"27.0.0","sha1":"924c185dc63638f00c55c30295579af1d497ab5d"},{"version":"27.0.1","sha1":"b10a97273de9b63298b8dbff8ffe6075b23da941"},{"version":"27.0.2","sha1":"61d7738dc3cdf0c11de0d246a2c805bfbc356a02"},{"version":"27.1.0","sha1":"318ba4f0b485f2cbabf63448d9ee8aa2a9c54488"},{"version":"27.1.1","sha1":"aa5905f10bb37253a0abcf91d2909e4f8f231ec5"},{"version":"28.0.0-alpha1","sha1":"9b46377aeae86f81c3f3952e9c76cdd5d81d99d6"},{"version":"28.0.0-alpha3","sha1":"8253912ac78ee1060035de5aa23fd502af699f78"},{"version":"28.0.0-beta01","sha1":"f614a5935c5387d0682ba1f661184ae9e5a1055a"},{"version":"28.0.0-rc01","sha1":"d922ea41e6239a01002f076f140dbe9668343ba5"},{"version":"28.0.0-rc02","sha1":"c522c2135435bbc6934f74da488263e4f6712325"},{"version":"28.0.0","sha1":"b605e979d4d4e17f399fbe7b52131c82748c510f"}]},{"package":"support-emoji-appcompat","versions":[{"version":"26.0.0-beta1","sha1":"7feea2e9928917afe547eed08d4e7f92ab6af886"},{"version":"26.0.0-beta2","sha1":"7d93370fe6760b0f672823d0404a8e0fc4d4364b"},{"version":"26.0.0","sha1":"609f22398956e5832e461791fa6228520d6d06df"},{"version":"26.0.1","sha1":"7cd6d5ccc21aca0eecd276faa2e5be01c4d0308f"},{"version":"26.0.2","sha1":"23ee5eb4b3f02b7334e41efa77e70c69237e4025"},{"version":"26.1.0","sha1":"e8ad0caa1d6031815fd39278b960ec448b290b60"},{"version":"27.0.0","sha1":"1e79a16b00718d0635be321dd2734e695a0798f2"},{"version":"27.0.1","sha1":"b821633c9f5352fd9b3aa28110564e1787c4a033"},{"version":"27.0.2","sha1":"e38d2464730e3fcdd71c5d9ab38cf001ac715f4a"},{"version":"27.1.0","sha1":"30456bce48e852da7409492dc9e0e70083cfe49f"},{"version":"27.1.1","sha1":"1e597c5cc111ac8e41923939712d7b364fb549b6"},{"version":"28.0.0-alpha1","sha1":"d4c56983de73c4750bd6f5d0713db4ae412afc85"},{"version":"28.0.0-alpha3","sha1":"143858faf7dfcc2a1189a6ac76fd8e594215bbc4"},{"version":"28.0.0-beta01","sha1":"bdec4e253e91f54c61d877ccb14da0a4926d0052"},{"version":"28.0.0-rc01","sha1":"56b6c2f74cf9a2d9c46aa0969549af2d4976a40f"},{"version":"28.0.0-rc02","sha1":"a0883146ae0a9399ae18b91a10bf4876723f3a07"},{"version":"28.0.0","sha1":"9135505cf8c5bcd1a36b92324f4673ff4e929e88"}]},{"package":"support-emoji-bundled","versions":[{"version":"26.0.0-beta1","sha1":"8df864b14a482707b36dfc5b1bddd9f186422720"},{"version":"26.0.0-beta2","sha1":"f9616725a113d67e47a19da625a5ead420f1c839"},{"version":"26.0.0","sha1":"bed904664f702887f263441771fabf9137d6618a"},{"version":"26.0.1","sha1":"e95cf6e1580897e68d48c81a778f39f799980a66"},{"version":"26.0.2","sha1":"9e387b92588d8f3c2a77759b07afc7c33feee876"},{"version":"26.1.0","sha1":"a237fd4de5111b1be7b2588913990ea1b70f90ee"},{"version":"27.0.0","sha1":"b9a2080bce04825ad008d8183f0db720c349fcc5"},{"version":"27.0.1","sha1":"cddcf17d469f0b5a85ac76d0db09573e7b498c17"},{"version":"27.0.2","sha1":"7cb44130f872f69ae00eacc62a8f7c8dda47a8df"},{"version":"27.1.0","sha1":"54f172cd300d667be737f8eec4fff74ff347a4dc"},{"version":"27.1.1","sha1":"df9054dc7d0069ba42aacb57dc4f80dfa8b7e08d"},{"version":"28.0.0-alpha1","sha1":"393d4cd267de49b226645a3977501b9608e45296"},{"version":"28.0.0-alpha3","sha1":"96e1305e523a8542eb575a42d8bb24d2b1e80688"},{"version":"28.0.0-beta01","sha1":"3da1888e9b1253766382c50950390bd7db277879"},{"version":"28.0.0-rc01","sha1":"61ce701744b3e2e1eab55519e7fe0f7409803885"},{"version":"28.0.0-rc02","sha1":"a96263b54a875995a4d505e916132160663c4b15"},{"version":"28.0.0","sha1":"266124db957216a447d80b7eb10790b8a9bf22cf"}]},{"package":"support-content","versions":[{"version":"27.0.0","sha1":"844caef192c18723f3f5b9ca6438c257a0a587b5"},{"version":"27.0.1","sha1":"fe4ac4543f528c260c0f5827011bf082e90982e9"},{"version":"27.0.2","sha1":"26c36cedda1a52a197e488d5f946508ffaaf4438"},{"version":"27.1.0","sha1":"879cf954db78b52e5de6a14202351deb80ee3969"},{"version":"27.1.1","sha1":"5e265e42ccda4d9b413d0cdc87b77ede8dc5506"},{"version":"28.0.0-alpha1","sha1":"e3d07ed8ece77473b36f9afab2b839bff41134d3"}]},{"package":"design-bottomnavigation","versions":[{"version":"28.0.0-alpha1","sha1":"662cabf582af74bca7030183ab6288e7bba68f3e"}]},{"package":"design-button","versions":[{"version":"28.0.0-alpha1","sha1":"14bb98b990e681d24bd28904b83b0857f2387cd"}]},{"package":"design-circularreveal-cardview","versions":[{"version":"28.0.0-alpha1","sha1":"538c8b63bc05ad561d59aeecfd0742a7cc058de0"}]},{"package":"design-bottomappbar","versions":[{"version":"28.0.0-alpha1","sha1":"25c23edd281289da282ddf3df4d7fd4dc5744305"}]},{"package":"design-card","versions":[{"version":"28.0.0-alpha1","sha1":"f6156fdd2fa98becd1dff9885c51543648628156"}]},{"package":"design-shape","versions":[{"version":"28.0.0-alpha1","sha1":"24b8c58f1b3962bb9394f7a55858e680b1db4406"}]},{"package":"design-drawable","versions":[{"version":"28.0.0-alpha1","sha1":"6f155368cc407705596b5f7445d3d7f918e96b2a"}]},{"package":"design-bottomsheet","versions":[{"version":"28.0.0-alpha1","sha1":"a3b93d98faae2cda648a78449a351b4f3eec61a8"}]},{"package":"design-floatingactionbutton","versions":[{"version":"28.0.0-alpha1","sha1":"748ca54cf815c6783a55680f2c90a367e866ba6b"}]},{"package":"design-circularreveal-coordinatorlayout","versions":[{"version":"28.0.0-alpha1","sha1":"dc0d59e124149bcd90872238d1427843e65148a8"}]},{"package":"design-textfield","versions":[{"version":"28.0.0-alpha1","sha1":"e7325de84531782e69239d3b85508e202c5dd86"}]},{"package":"design-stateful","versions":[{"version":"28.0.0-alpha1","sha1":"dbd7b1747c50696226f66d124ee992a19cbbcfbe"}]},{"package":"design-circularreveal","versions":[{"version":"28.0.0-alpha1","sha1":"dc0230faa1ea8a3d098f240328d7ac24e55838cf"}]},{"package":"design-expandable","versions":[{"version":"28.0.0-alpha1","sha1":"cc20aaf98dd9c2d997d9f68678993af616d9de23"}]},{"package":"design-navigation","versions":[{"version":"28.0.0-alpha1","sha1":"19d14a94f9296d7d4b1ebd826e1a8a0b8cc3c69d"}]},{"package":"design-dialog","versions":[{"version":"28.0.0-alpha1","sha1":"8206faaac0d33f22969ca79defb808e6911cda85"}]},{"package":"design-canvas","versions":[{"version":"28.0.0-alpha1","sha1":"f8648367ba145c0f73730613d10b0568d4f26f7f"}]},{"package":"design-tabs","versions":[{"version":"28.0.0-alpha1","sha1":"c71c64229bac90b94f507d3673149869a072092f"}]},{"package":"design-chip","versions":[{"version":"28.0.0-alpha1","sha1":"851ba8db73c6d4a457d5418385c194250036f3d"}]},{"package":"design-snackbar","versions":[{"version":"28.0.0-alpha1","sha1":"ff021520957d19f4547ad80ebe94cb5b57f8fa89"}]},{"package":"design-theme","versions":[{"version":"28.0.0-alpha1","sha1":"f64a53780eab9b1f0e4910254c1c71e3145cf91"}]},{"package":"design-math","versions":[{"version":"28.0.0-alpha1","sha1":"83e8f0b23aeb8ac5c78d53e6fe69e03a31e15e67"}]},{"package":"design-transformation","versions":[{"version":"28.0.0-alpha1","sha1":"b5c694a3bed4c2c4a218a84947e84e7437279716"}]},{"package":"design-widget","versions":[{"version":"28.0.0-alpha1","sha1":"3eb3123dd4ec31601f2176de41b07ca042a95b39"}]},{"package":"design-animation","versions":[{"version":"28.0.0-alpha1","sha1":"567ed151c151e280f4649844070e649d06a5d45e"}]},{"package":"design-typography","versions":[{"version":"28.0.0-alpha1","sha1":"1c9d37cecbaaa2d205db4d916beefb26dc385ec3"}]},{"package":"design-color","versions":[{"version":"28.0.0-alpha1","sha1":"7150d6e358e383c35e5be5f4f2dd93a97a8f9d30"}]},{"package":"design-internal","versions":[{"version":"28.0.0-alpha1","sha1":"ba73ad0084f5ca78e6c0cb5fb01976bc2af19376"}]},{"package":"design-resources","versions":[{"version":"28.0.0-alpha1","sha1":"f5dad4f63435e0851982925f1210d5483d9b3707"}]},{"package":"design-ripple","versions":[{"version":"28.0.0-alpha1","sha1":"2042c5eaf460a3d92194126cb6e8672d46dcbc87"}]},{"package":"coordinatorlayout","versions":[{"version":"28.0.0-alpha1","sha1":"a5b192a1c661f2d53ae6892eaf7c7a729ef5dd09"},{"version":"28.0.0-alpha3","sha1":"6aa0d6560ddc307fa89fde11d6fd34c4c7c1a5a8"},{"version":"28.0.0-beta01","sha1":"4ad519e6f4aff962fb63996930ded851210bc8b6"},{"version":"28.0.0-rc01","sha1":"7f7028fbdefdc6ab8e5d3681572567db4311ea4d"},{"version":"28.0.0-rc02","sha1":"be7854ec0cb93027035a2992db1aa5abf3339f19"},{"version":"28.0.0","sha1":"7a708aac3443762e58e84368040a6a23b2c63545"}]},{"package":"collections","versions":[{"version":"28.0.0-alpha1","sha1":"ec466d024148ba1bb325181625c9bb92e461d39b"},{"version":"28.0.0-alpha3","sha1":"a037a9517c0b0bdaae96b21128534af4ba706c0c"},{"version":"28.0.0-beta01","sha1":"f6f0b90b6de8d144148ccd6b9d2c869a35347b0d"},{"version":"28.0.0-rc01","sha1":"936bb431338ec51ccd899b3bf100b3961af3c25d"},{"version":"28.0.0-rc02","sha1":"9b8a0db9ca147cfe2c2d603d0a1ad6021543eb1f"},{"version":"28.0.0","sha1":"c1bcdade4d3cc2836130424a3f3e4182c666a745"}]},{"package":"slidingpanelayout","versions":[{"version":"28.0.0-alpha1","sha1":"c761042a24c5c47cd04df1bca9c198b9a93d15bd"},{"version":"28.0.0-alpha3","sha1":"971e11a925eafb8402ac85716349bee85c1927f3"},{"version":"28.0.0-beta01","sha1":"373364d1d992052eaba9764e794472641de33404"},{"version":"28.0.0-rc01","sha1":"8b1398a2b3a43c46af31f80ce9158d936c81944b"},{"version":"28.0.0-rc02","sha1":"fb0b44317a76b27b8d1a15ff51928f60cc67a249"},{"version":"28.0.0","sha1":"20468e3ec8f36dc84846ddd99ff30516f4ffd05a"}]},{"package":"asynclayoutinflater","versions":[{"version":"28.0.0-alpha1","sha1":"47fdcf43e4a31bbc3a3523ad4d33630e60af4944"},{"version":"28.0.0-alpha3","sha1":"803de7c5ea2b673e28378c4bee1d4b8294c21b3"},{"version":"28.0.0-beta01","sha1":"d08594625621f97502d9e4991a08b2ed8726a457"},{"version":"28.0.0-rc01","sha1":"fd742c53eb75edbfb34e12a314ede9df18105862"},{"version":"28.0.0-rc02","sha1":"d928a5de7cc87ac5284730d65db0f74eee50ae1f"},{"version":"28.0.0","sha1":"3ae7643d120e6da3adbe2d698de923f48c904d1f"}]},{"package":"slices-view","versions":[{"version":"28.0.0-alpha1","sha1":"ec9191c86d9ec85fe26418716bccfa67e027dcff"},{"version":"28.0.0-alpha3","sha1":"8c70c428d3407349cb38494486356288e85e8508"},{"version":"28.0.0-beta01","sha1":"fa9f84323e6ce95aef94d983af795372974ceccf"},{"version":"28.0.0-rc01","sha1":"34bffd26c278aad003b8d2ec2fa2f5ddf8cf4cca"},{"version":"28.0.0-rc02","sha1":"141f04cb911514770b29dd837622b61378828736"},{"version":"28.0.0","sha1":"6e92b6e3dd111c17140c8d8d6a92afef6fa17187"}]},{"package":"recyclerview-selection","versions":[{"version":"28.0.0-alpha1","sha1":"acabfc9ecb18d61811b3cc31b70f9c22a8ec09d9"},{"version":"28.0.0-alpha3","sha1":"34db99323ddeee10d93c00c513d5da04a64eb23d"},{"version":"28.0.0-beta01","sha1":"e1249e020c37f38a2f7c31a4ac933b30f5c3c3b0"},{"version":"28.0.0-rc01","sha1":"ef0180b80eb03d123a61e92e6a077c4c7f90bd9b"},{"version":"28.0.0-rc02","sha1":"d2db88b765f0a0d0669450616306448fcdc7b038"},{"version":"28.0.0","sha1":"a4131b949d3d6580028f1c38c53db279c1613e18"}]},{"package":"viewpager","versions":[{"version":"28.0.0-alpha1","sha1":"3bea024fe2aeb4e748d0823226e2de675236c7d1"},{"version":"28.0.0-alpha3","sha1":"7f0102b9780779ce564208b9a2afe3bcc4ec4c57"},{"version":"28.0.0-beta01","sha1":"282b871a23fe6749dcdd0c8f68bfe6be6c6c3bdc"},{"version":"28.0.0-rc01","sha1":"d62341459323f10503279d3f9f27acc379d5efaf"},{"version":"28.0.0-rc02","sha1":"5a3501325b7ec401fa4fb2b7cc31ace0d7b20b0f"},{"version":"28.0.0","sha1":"f513ecf69dfea8b60987bd3e869970300ba7c0eb"}]},{"package":"cursoradapter","versions":[{"version":"28.0.0-alpha1","sha1":"e7588e0ef4caa61b4626bdb98e3a82678b7cb743"},{"version":"28.0.0-alpha3","sha1":"9c31df7508af12d26a0d35e20553e49f3fc92ca8"},{"version":"28.0.0-beta01","sha1":"62a46a2522d695b12014eed9a1f72ed831119f10"},{"version":"28.0.0-rc01","sha1":"c99c0ae27dfc969789e8c2810e2f8c9162abbeaa"},{"version":"28.0.0-rc02","sha1":"dc8c67f2d4aa7d8ea59f6474bb0084467a375af2"},{"version":"28.0.0","sha1":"d803f573799e6cd2db8839e2a70fe6ad67e86b79"}]},{"package":"localbroadcastmanager","versions":[{"version":"28.0.0-alpha1","sha1":"665df02e1c6101423a20058a496c4f6e484761ab"},{"version":"28.0.0-alpha3","sha1":"c41350c14349e34dd2c829e1df68f53fd365c58a"},{"version":"28.0.0-beta01","sha1":"bb34ee10aef275da13d1ab8b6e462cd9011c7190"},{"version":"28.0.0-rc01","sha1":"e2075d0ad21b261caca814c3393de6799696799a"},{"version":"28.0.0-rc02","sha1":"a001e8b8a23a1a72298a94b758ea0b230c9d1fc0"},{"version":"28.0.0","sha1":"5c498cb7e2fa5910d6c50e28531c55b77d6bf0f6"}]},{"package":"heifwriter","versions":[{"version":"28.0.0-alpha1","sha1":"7b52dab935c18b0163e8896cf6e01b5aca406686"},{"version":"28.0.0-alpha3","sha1":"523d5b78aa18c1bd7a21aa7dee3081501c889063"},{"version":"28.0.0-beta01","sha1":"8ba3dd9b3f1cbcb2a59ff37d26f25a4b05b5e3c3"},{"version":"28.0.0-rc01","sha1":"cfd1f3de7d7417ab5b3b2b7dae784c8aa8cd72ac"},{"version":"28.0.0-rc02","sha1":"a2d3d28a59d4de4ab3b066ead97e0933bf65780d"},{"version":"28.0.0","sha1":"45fa07536d42ef9f0517cf0a9d344b2c99e0a1ba"}]},{"package":"customview","versions":[{"version":"28.0.0-alpha1","sha1":"e73cafad3c83bd61b1412eb313b34eaf033e57c5"},{"version":"28.0.0-alpha3","sha1":"e918a83447c6de180db4aa4b2b50ce02197cf739"},{"version":"28.0.0-beta01","sha1":"d8ef94a53162e1aa7728aeb225813c870053076d"},{"version":"28.0.0-rc01","sha1":"b84403eb6f2484b4dde74032765e2bdce41b0278"},{"version":"28.0.0-rc02","sha1":"c548091c29aaa3343dde46e91d06db30c1db1841"},{"version":"28.0.0","sha1":"423fe0f417f2f8d9c718c2cf73f9253da43f1f11"}]},{"package":"print","versions":[{"version":"28.0.0-alpha1","sha1":"1e91d7d8d3bd4a66baecdeaa3a4bd658ae56a507"},{"version":"28.0.0-alpha3","sha1":"33ec6f207e9be714527b42b3612241ad4526eee0"},{"version":"28.0.0-beta01","sha1":"2312d202a7d0ab73ffd138dcd38a45c866c95636"},{"version":"28.0.0-rc01","sha1":"57a9756bca8bcd01376075918f56120a290cf06"},{"version":"28.0.0-rc02","sha1":"58e756d2f6ee857674a1bc1d52a350b843aa11c"},{"version":"28.0.0","sha1":"d2c60bfbbdc2eadd4ff7c8f65743fab830339743"}]},{"package":"slices-builders","versions":[{"version":"28.0.0-alpha1","sha1":"fb455942c0d9a24ce445101bbb25cdb86dc05526"},{"version":"28.0.0-alpha3","sha1":"6dee783fb6a449bef6db7ddc49605bac3324e0a1"},{"version":"28.0.0-beta01","sha1":"fcfcbcd5af540e93c5ea5f25f7ea20576ecdcf7e"},{"version":"28.0.0-rc01","sha1":"f59378b24e450fbb7f240805824c2c3baaa6e820"},{"version":"28.0.0-rc02","sha1":"ddbf3f5c3cfd666cb791daae9044bd184709d51c"},{"version":"28.0.0","sha1":"31ff0ca86781fcbc4afb50789a76d21b61f84070"}]},{"package":"interpolator","versions":[{"version":"28.0.0-alpha1","sha1":"a7b48a834381e3037d30f8e21081a7cb02e72de9"},{"version":"28.0.0-alpha3","sha1":"28a0c8c6c0aa651beb3a5dc2dd0ed1802ae050ab"},{"version":"28.0.0-beta01","sha1":"b8da87d594e3d507fca59a28fbb0744992e26295"},{"version":"28.0.0-rc01","sha1":"a4c9b6a8fbaf8055cd912056df7b22bb53003783"},{"version":"28.0.0-rc02","sha1":"eeac39b9c0bc6cf59cc54b3c0ca207f911eace83"},{"version":"28.0.0","sha1":"5d501569c8f7b667c47333a0b873aa529e0a0b9c"}]},{"package":"slices-core","versions":[{"version":"28.0.0-alpha1","sha1":"3cd874b98b4a1fe5c5ceefd7d662a3a7c2023dcd"},{"version":"28.0.0-alpha3","sha1":"c11a0c085e544411870e96d2635a3bb9eec4e6a8"},{"version":"28.0.0-beta01","sha1":"c7a02b7e1963e4587402f5235cd0163d0bdcc112"},{"version":"28.0.0-rc01","sha1":"a0f29cabb8405e247b8f835c06f02495a8d9297e"},{"version":"28.0.0-rc02","sha1":"4a28528c8cb5d64c5b9a4ab072b50b14e577469b"},{"version":"28.0.0","sha1":"48cbbace8b4437ac7db28d41c4e70f0f1aadab30"}]},{"package":"loader","versions":[{"version":"28.0.0-alpha1","sha1":"4f4dc25df929a5b728e3cca9ede3df03323fba8e"},{"version":"28.0.0-alpha3","sha1":"677fbf1a93d2e8448822e1a2e5321ab972036f9d"},{"version":"28.0.0-beta01","sha1":"64b065f3df62635529ed34c22e88123303eeaa95"},{"version":"28.0.0-rc01","sha1":"15897baf3bcd0355c575900c2eabaa4dd4aff2a5"},{"version":"28.0.0-rc02","sha1":"cffbec79044e0139dd532ee63ab9510ddf08cbc8"},{"version":"28.0.0","sha1":"49a297a4635e01ed55f31b5d4a718ba3416fde3d"}]},{"package":"swiperefreshlayout","versions":[{"version":"28.0.0-alpha1","sha1":"9a8658d770e08cbddd75bedf68d77dd948c42cac"},{"version":"28.0.0-alpha3","sha1":"8a39ee6513bfed3ef189a4dd0cab14cb8438d0a6"},{"version":"28.0.0-beta01","sha1":"9d97fabb3eee07e5749708bab84cca2bddb247b"},{"version":"28.0.0-rc01","sha1":"d031ce2bc71da2dfaeed57232f550f1425c7c9f2"},{"version":"28.0.0-rc02","sha1":"18a413224429acde26ba390d48b2ef385fbac4f4"},{"version":"28.0.0","sha1":"bfa669303f0ac8a83d9c878fafadc2936625f781"}]},{"package":"drawerlayout","versions":[{"version":"28.0.0-alpha1","sha1":"771b1e91ba8ed3ee7097d235623401a6a1b5063a"},{"version":"28.0.0-alpha3","sha1":"9880569de28b8dcbc69e4ef671e86ab478a241"},{"version":"28.0.0-beta01","sha1":"c1be409fb06fa35edb14f98718ed3a3333763fb6"},{"version":"28.0.0-rc01","sha1":"a52a2224522171a708ab9d8605971a1de2443384"},{"version":"28.0.0-rc02","sha1":"d025665ab5e68e2ff24ad29eda831d4a2dba0ba7"},{"version":"28.0.0","sha1":"4de65d42b8e1b7f0ba40b5f35e5d4bafcd70019f"}]},{"package":"documentfile","versions":[{"version":"28.0.0-alpha1","sha1":"156c7a1cc50c0dff33e3adaf5f1b512a27a24989"},{"version":"28.0.0-alpha3","sha1":"9d6373ce29a3d64c6d4b6c3ed210464c2141dbce"},{"version":"28.0.0-beta01","sha1":"759ab9f6b3e84a8cbd993dccf1f10935939e768f"},{"version":"28.0.0-rc01","sha1":"cb3260177e605472dd33d82d13f2673ddb00cec7"},{"version":"28.0.0-rc02","sha1":"81126b67450817ec8fb729cd226cbfb3e3c659c1"},{"version":"28.0.0","sha1":"1187e4a23ff6250b096249c734bdabf5403c6ba9"}]},{"package":"webkit","versions":[{"version":"28.0.0-alpha3","sha1":"2d5bd763cfad32350142c760984dccc0c5d7a893"},{"version":"28.0.0-beta01","sha1":"8c8a7008081f7d9bc4ab38c7003e9acf4c6e25d5"},{"version":"28.0.0-rc01","sha1":"6ee32609781ac785bf5e7bfe88a9ba229e41f53d"},{"version":"28.0.0-rc02","sha1":"aeac5a923854bfee34e2d46b7b205848331ce5da"},{"version":"28.0.0","sha1":"8abbcb9d7e8da25d26f1898999fd69292a173005"}]},{"package":"car","versions":[{"version":"28.0.0-alpha3","sha1":"8225164b8714a9e9f1b94f486989124326d2382d"},{"version":"28.0.0-alpha4","sha1":"d3fb5fa08f90bf5aa9271a7055797e69902097b3"},{"version":"28.0.0-alpha5","sha1":"3dbacb3bace9d6e3f5cd191efded6fc625ef2ce3"}]},{"package":"versionedparcelable","versions":[{"version":"28.0.0-alpha3","sha1":"e9b95b3c3a336820cb44d560cde864a0b9b46e03"},{"version":"28.0.0-beta01","sha1":"6b4010e6d1e6362c7ed6caf633d1d9d4640bde29"},{"version":"28.0.0-rc01","sha1":"e49cbae636cd4e4c7667ae3d0b069682a02bce62"},{"version":"28.0.0-rc02","sha1":"127a223c8a925b028c295adbf3966f6948bf4ec1"},{"version":"28.0.0","sha1":"90432a1e322e0e0bad2116dff0e64c708514808f"}]},{"package":"media2","versions":[{"version":"28.0.0-alpha01","sha1":"e003498fa91835fcfe012a432b6ac93e476fd39"},{"version":"28.0.0-alpha02","sha1":"6a6d3fa2a00ed1f9578a1e677be5abd862b58a6f"},{"version":"28.0.0-alpha03","sha1":"fcea940d00a8fcb2b3bd4e9fe895e0e34d69540f"}]}]},{"group":"com.android.support.test","update_time":-1,"packages":[{"package":"runner","versions":[{"version":"0.2","sha1":"d04fd92c621f1c85a4378b7ccc85afe01bdb289c"},{"version":"0.3","sha1":"a31e7e8db98ca19fb3fab23f120d19a6f4e3e8a9"},{"version":"0.4","sha1":"9073e42c0bb9f1ded21d00a25fa771fc76f693fc"},{"version":"0.4.1","sha1":"8b15f1a1d5a94df868842103d268162985e7b8b4"},{"version":"0.5","sha1":"73f9e89fcb4c1a43a0894e294f6efa3df7b72a2c"},{"version":"0.6-alpha","sha1":"f21684db2d291352eac25f096c898b346b51fffc"},{"version":"1.0.0","sha1":"ba0fcc6e319d3bc1564543c8b210b01b22576ec5"},{"version":"1.0.1-alpha-1","sha1":"f377b203524e915469c5908adf26108f9f99bf7d"},{"version":"1.0.1","sha1":"b39d2562a34213ac4772d53b618f44500f8403d7"},{"version":"1.0.2-alpha1","sha1":"131c4e64707c920455f3f9000636f149f324f054"},{"version":"1.0.2-beta1","sha1":"67336f35dd108d0b00211f8ec247457ee638cb01"},{"version":"1.0.2","sha1":"f88e837a5fdf63ed6c9d9852816d929f958cbec7"}]},{"package":"rules","versions":[{"version":"0.2","sha1":"8bc715ea76fcb8cfdcf667840db0da445a112210"},{"version":"0.3","sha1":"eff09ea47ca1c7a383cb2dd5ec95724e4e5fd43b"},{"version":"0.4","sha1":"a9fbad104ec64d8867e74bb6f8d09257147c6a00"},{"version":"0.4.1","sha1":"989d12fe4d83fe0dfecb48458ee644455ef7a9c4"},{"version":"0.5","sha1":"f62d39eb9fede7f037699965d10188d992e835f6"},{"version":"0.6-alpha","sha1":"d8c908a403ef411cdff1fb4d4b6a014cb7cf6d1f"},{"version":"1.0.0","sha1":"2c0ee1f662af6099e87b8f176eb25d2a7de82a30"},{"version":"1.0.1-alpha-1","sha1":"afc664aefa2eb399dc8cce3e87d795fb0822a9a7"},{"version":"1.0.1","sha1":"afc664aefa2eb399dc8cce3e87d795fb0822a9a7"},{"version":"1.0.2-alpha1","sha1":"2a07e19d420ec0544dab09688088a989eb132454"},{"version":"1.0.2-beta1","sha1":"e504eb786542af6c2f57242375350fcdd4a35b1f"},{"version":"1.0.2","sha1":"e504eb786542af6c2f57242375350fcdd4a35b1f"}]},{"package":"exposed-instrumentation-api-publish","versions":[{"version":"0.2","sha1":"13f587cfe67063f693ad375db0dfbdf04349b574"},{"version":"0.3","sha1":"a7161eafdfbd02a39461f076c9dce0c8e5e7a149"},{"version":"0.4","sha1":"48115b400b94b184f299a959f3dd6888a4fd5e4b"},{"version":"0.4.1","sha1":"731fd13cda8e8c95f200202a73cec5420748b351"},{"version":"0.5","sha1":"d46bd0c37b1c193f2f53b6c21efc222077113b80"},{"version":"0.6-alpha","sha1":"1d9a633a702d92c01b2b4ed02253c1832f97c77f"}]},{"package":"testing-support-lib","versions":[{"version":"0.1","sha1":"36e4f08b2a3389dd5a5093411df35091bdd68361"}]},{"package":"orchestrator","versions":[{"version":"1.0.0","sha1":"318ebd5f7cd9ab7b9e2fee06e6894bca8248e363"},{"version":"1.0.1-alpha-1","sha1":"32831154ba91117bd5a5fb22752a2355c439ac73"},{"version":"1.0.1","sha1":"12d61be26b643c6413d207248660bce8f6d8b236"},{"version":"1.0.2-alpha1","sha1":"fd9d015f1a9470c71a653354b7d46427b96af392"},{"version":"1.0.2-beta1","sha1":"7b5694d7dead64df8534beb9aa3f06e30256b938"},{"version":"1.0.2","sha1":"a3e051dc739ac51d6cf2813ad0df598f46d11e2"}]},{"package":"monitor","versions":[{"version":"1.0.2-alpha1","sha1":"87a84b5f532770974a59dda7e1a5ef3319224789"},{"version":"1.0.2-beta1","sha1":"efc2ae9c93cc1d234be35a2406a9a423b6ee5d75"},{"version":"1.0.2","sha1":"efc2ae9c93cc1d234be35a2406a9a423b6ee5d75"}]}]},{"group":"com.android.support.test.janktesthelper","update_time":-1,"packages":[{"package":"janktesthelper-v23","versions":[{"version":"1.0.0","sha1":"b66207fa99fbfc6fe07825ce792fcdc30031c093"},{"version":"1.0.1","sha1":"19a858c273b327554449d290ec52dbb168fdb6ab"}]}]},{"group":"com.android.support.test.uiautomator","update_time":-1,"packages":[{"package":"uiautomator-v18","versions":[{"version":"2.0.0","sha1":"5998bd2f44c836cf480ebef74179d9ca8b1d1787"},{"version":"2.1.0","sha1":"ce7b8c581d3f5d904677256197d5773212ae1981"},{"version":"2.1.1","sha1":"2df131c5ae0ba49b23865e6f873ea471bbd1c37f"},{"version":"2.1.2","sha1":"30c8b9c8fffe1f06c0e8ed86e76f758ea1a5314f"},{"version":"2.1.3","sha1":"c55d6c764df674b6432fe6b63bbfcca5c55aaf04"}]}]},{"group":"com.android.support.test.espresso","update_time":-1,"packages":[{"package":"espresso-core","versions":[{"version":"2.0","sha1":"48e17db3a82e94be7c86a8f9ab61b381b15dc738"},{"version":"2.1","sha1":"6843e276ff5bd08e227ba09341af2fb8903e5ae5"},{"version":"2.2","sha1":"557f9abc806498ed22b936cde1144552e60e5c51"},{"version":"2.2.1","sha1":"7cca5f8023903970c4dcec7a10067570fe3d85cb"},{"version":"2.2.2","sha1":"9463febe11441cdbae07a5b6cddff972279ae991"},{"version":"2.3-alpha","sha1":"3bd1635751e85990c1b722ac6bd9d311001061a2"},{"version":"3.0.0","sha1":"79501e10e8f7453dc07b924c995ac2a4b047c77"},{"version":"3.0.1-alpha-1","sha1":"ac158e627393d6c7e44ae4a2241c7f38d50221c2"},{"version":"3.0.1","sha1":"bd1ab0e4368dcdb2ddc4f40a7223ebbc205f9fd9"},{"version":"3.0.2-alpha1","sha1":"ae85f281f4038e748bb0e01fce9e49711f7f958b"},{"version":"3.0.2-beta1","sha1":"7f9ad975564836eba1de308e46a967cf1db83fd9"},{"version":"3.0.2","sha1":"4625e05585025ac9feee0ca2e29557ca61d4789e"}]},{"package":"espresso-web","versions":[{"version":"2.2","sha1":"2566364793b5d45b165deb65f0ee5f798c24fc15"},{"version":"2.2.1","sha1":"6602f010289b4b904e975aeaeb23190e121b162a"},{"version":"2.2.2","sha1":"33b585514e461e5f5ad4ff4dbb316b507a948184"},{"version":"2.3-alpha","sha1":"fd1318c3439b9ffd030ec03d431dd3f5f0fd83f2"},{"version":"3.0.0","sha1":"8eb43c67cb8119d1037975cc3488a8c945b07985"},{"version":"3.0.1-alpha-1","sha1":"e5b2d4cd3bac8204d01ab5b1d34b8cc26fdd80c1"},{"version":"3.0.1","sha1":"e5b2d4cd3bac8204d01ab5b1d34b8cc26fdd80c1"},{"version":"3.0.2-alpha1","sha1":"e6f926241a23a48fad9a37d7f7a6bc9816214da"},{"version":"3.0.2-beta1","sha1":"9e09454a05dfbfff4c43b228e7635b36985a37a2"},{"version":"3.0.2","sha1":"90fa47c534726979b8be8187b3e0a5d38a1740f1"}]},{"package":"espresso-intents","versions":[{"version":"2.1","sha1":"91b551ccc6d1a9b2e582e95d99ee5b3d7c05a564"},{"version":"2.2","sha1":"54ceef29e9488beb768f3ae2950b9f7301ed0452"},{"version":"2.2.1","sha1":"931caa0752967df8dda3cd3ccab448ab3e348adf"},{"version":"2.2.2","sha1":"389db8260854c43b805b2c8ec1cea393bd8d8d36"},{"version":"2.3-alpha","sha1":"f8690644269189fa584cb3479d2026eb48f10cde"},{"version":"3.0.0","sha1":"e700485a1882536ceaad592e7330c4a0c800bd00"},{"version":"3.0.1-alpha-1","sha1":"e700485a1882536ceaad592e7330c4a0c800bd00"},{"version":"3.0.1","sha1":"e700485a1882536ceaad592e7330c4a0c800bd00"},{"version":"3.0.2-alpha1","sha1":"8297d555a50a7015cbd3452e01a9e2039a186b3"},{"version":"3.0.2-beta1","sha1":"4c244c863c3821116b99b61b7bcec5bdc1b1e79"},{"version":"3.0.2","sha1":"b67cf8a406789476a4b47475008787f90c30cd2c"}]},{"package":"espresso-contrib","versions":[{"version":"2.0","sha1":"f82c9182a1d84a9a573486db05a9373eac696812"},{"version":"2.1","sha1":"2839002b1ff8000ca4c7a0835a75ff61bf5980d3"},{"version":"2.2","sha1":"8026b2440392a87f393cf827987959a555e57c47"},{"version":"2.2.1","sha1":"cdd216056a9195fbcb4492b8d464fec0db10fdd3"},{"version":"2.2.2","sha1":"264b8664ff3a934360f666cb9b70b0851a710f7c"},{"version":"2.3-alpha","sha1":"c638f0ce9acb736b659afb698cbcea5eff32bd3e"},{"version":"3.0.0","sha1":"614c54dcbc1d71bed80b24e4f803f508041af062"},{"version":"3.0.1-alpha-1","sha1":"50082c25ff6bfa6ec13c506e985d3699924970a4"},{"version":"3.0.1","sha1":"50082c25ff6bfa6ec13c506e985d3699924970a4"},{"version":"3.0.2-alpha1","sha1":"3a8aa8425eee03fcd7a9848f8b654ea8cea0694a"},{"version":"3.0.2-beta1","sha1":"b43ab6fb2973bc029c0e530dc77d8df345e48526"},{"version":"3.0.2","sha1":"71ca893cba7c76839c731b11defe219b98740c11"}]},{"package":"espresso-idling-resource","versions":[{"version":"2.0","sha1":"720d4da6f0648a9825bbfc2539025066713f26c7"},{"version":"2.1","sha1":"ad4a3a71dcaa012f638d49d6e0748440e067d0f7"},{"version":"2.2","sha1":"851421b559269e7d418817968f5d68583a062a74"},{"version":"2.2.1","sha1":"4b74b25dc81227123530ac13aaf3b4dd67e9355c"},{"version":"2.2.2","sha1":"98edb721e12497fcd316159fe3492ccf98a01b33"},{"version":"2.3-alpha","sha1":"551890849571b87ed25382a1d8080e629a41a8f4"},{"version":"3.0.0","sha1":"f8b9b0941efee852471ebcd08875d4a5bcdfc0f3"},{"version":"3.0.1-alpha-1","sha1":"ab8fc778d9914d3140676e32fd955bc56389eb2b"},{"version":"3.0.1","sha1":"ab8fc778d9914d3140676e32fd955bc56389eb2b"},{"version":"3.0.2-alpha1","sha1":"2b8db7418fd78f2d3789133cc20db128bada2ee6"},{"version":"3.0.2-beta1","sha1":"8c7abeeaf5a5c6544e7007aa22aba3d8025d9e1e"},{"version":"3.0.2","sha1":"8c7abeeaf5a5c6544e7007aa22aba3d8025d9e1e"}]},{"package":"espresso-accessibility","versions":[{"version":"3.0.0","sha1":"ed778c68f14f32f2e65e38db32febaed4e4c19f7"},{"version":"3.0.1-alpha-1","sha1":"e8c874befb6f7cb720cb25d8bca5fea9dd6318a4"},{"version":"3.0.1","sha1":"e8c874befb6f7cb720cb25d8bca5fea9dd6318a4"},{"version":"3.0.2-alpha1","sha1":"1c5b3eb8733a0e72ba4498a65a885acb5d6b4567"},{"version":"3.0.2-beta1","sha1":"b206cc5585ae2becc8c30981b945e1e259554c21"},{"version":"3.0.2","sha1":"5fb19b0b6d790604fa9e42c21467d4696d5ffad7"}]},{"package":"espresso-remote","versions":[{"version":"3.0.2-beta1","sha1":"7633db553cedf53c516b1145c892f0b4adb7f968"},{"version":"3.0.2","sha1":"4b241e89a51bb3b74d11d5ba24c51109d4722f74"}]}]},{"group":"android.arch.persistence.room","update_time":-1,"packages":[{"package":"compiler","versions":[{"version":"1.0.0-alpha1","sha1":"ce0f0544adf7530169d13c97b5c75f91147421fe"},{"version":"1.0.0-alpha2","sha1":"b79d21887eed2529a5637ff7e3bb46d4f1ce8c50"},{"version":"1.0.0-alpha3","sha1":"d1600a4b54e8f776449750eeca85d9f6e8f76fad"},{"version":"1.0.0-alpha4","sha1":"ac8fa4fe6e271eda4ef717aab21f62ecbacee671"},{"version":"1.0.0-alpha5","sha1":"5072f51a263ab0a5c01c53b17bdfca92ea4371c8"},{"version":"1.0.0-alpha6","sha1":"58ebed7429ce3154b5b5bc238fb3b54668e26ee9"},{"version":"1.0.0-alpha7","sha1":"bbe0d81e6b6a85d58731bedc47dba4a0e7594cd1"},{"version":"1.0.0-alpha8","sha1":"49c163b1f268b0f4a8ef667ed7fb46016fe70b8e"},{"version":"1.0.0-alpha9","sha1":"ebb854ecd78c537cedf150729549ce4e56b8b155"},{"version":"1.0.0-alpha9-1","sha1":"b264bf594f3af7789ded9eb2944163cbd016cb96"},{"version":"1.0.0-beta1","sha1":"3a217aa13169758929289d62e7da6db8dbc8b3d"},{"version":"1.0.0-beta2","sha1":"676e4863aebc208f39ae1d889b8aba38f7be96bd"},{"version":"1.0.0-rc1","sha1":"6e7e26e03eaad0be85f3839eacf75e3a007cf71c"},{"version":"1.0.0","sha1":"5d06e00cf902bc7e198eef8bbb1394bef5ae0c21"},{"version":"1.1.0-alpha1","sha1":"d21dd0ee9ae5a1ff1980ef8551c3a6d9b9c66f71"},{"version":"1.1.0-alpha2","sha1":"8a5291181fc42a19cd9db48d1c27a611ae4af86d"},{"version":"1.1.0-alpha3","sha1":"793e55c89ec8c5127ce129b3884b8a894765db8d"},{"version":"1.1.0-beta1","sha1":"33f4303edecc521ee4eb680622926f02119b52d8"},{"version":"1.1.0-beta2","sha1":"7c902c33e0dcdf1eb1695bdd60dbc5d7deb56216"},{"version":"1.1.0-beta3","sha1":"bd2ba2bb44f8bea509efa3c6553eff5297096377"},{"version":"1.1.0-rc1","sha1":"e9a53ef1b22ecc0835b3e9e254b9a92389f34a35"},{"version":"1.1.0","sha1":"e7dbc2fdf7fc86b0275ea5198616d5c764238a11"},{"version":"1.1.1-rc1","sha1":"6288ee3b62aaa4d908a4b8b25395ae33536c50bd"},{"version":"1.1.1","sha1":"53037626f1971006fcf8e112594c6902efafff6"}]},{"package":"support-db-impl","versions":[{"version":"1.0.0-alpha1","sha1":"d222a2d4fe3efc594ed3240f0c4c1222acf2d9b6"},{"version":"1.0.0-alpha2","sha1":"899238fa9e392b1d39afdf543c9e161ea27c77ce"},{"version":"1.0.0-alpha3","sha1":"9e007a0cfb866ea52b67051734def3a5085d4a06"},{"version":"1.0.0-alpha4","sha1":"28c31eadf542d0e42b7d03976c12fd29ac2d099d"},{"version":"1.0.0-alpha5","sha1":"87315f6c88cf0e3c8b26e182095dfad24f5a1472"},{"version":"1.0.0-alpha6","sha1":"a59c9b8d2ba4e69393d7104444866c0cad2fb153"},{"version":"1.0.0-alpha7","sha1":"7e334fe7b248957a8b166889b9d5f82a8dcde396"},{"version":"1.0.0-alpha8","sha1":"a04b8887f175f1f2baa1531e8fa7f7beae8c3480"},{"version":"1.0.0-alpha9","sha1":"974b1dd2920a44cb1d703dcdfc04a1090d1a2c8f"}]},{"package":"runtime","versions":[{"version":"1.0.0-alpha1","sha1":"72b1fd937625441867ee71d898db694263cbe443"},{"version":"1.0.0-alpha2","sha1":"3236d40e9cb3039c62f5df4932bb1045f5a1d633"},{"version":"1.0.0-alpha3","sha1":"40565727788afbee5328273660943458047a6b4f"},{"version":"1.0.0-alpha4","sha1":"11fbd33cf64e5aa4bc23d7691fb5b63da901e843"},{"version":"1.0.0-alpha5","sha1":"89afad706c932b1f0201b2f412200227a792af34"},{"version":"1.0.0-alpha6","sha1":"49a878ff03f4403c2f4f5beaf6beec2008c792cd"},{"version":"1.0.0-alpha7","sha1":"2cc986e4cd603e88b6ace45497bb480fc8d6703"},{"version":"1.0.0-alpha8","sha1":"f52633ac54aae93d3ec752ed5fde7e1f4f4beba9"},{"version":"1.0.0-alpha9","sha1":"1c16ceb681098ae326d3f8293cf68fbd00ad0a63"},{"version":"1.0.0-alpha9-1","sha1":"367587088b16b2fd5ed17d8358cb50dfd8fddfda"},{"version":"1.0.0-beta1","sha1":"f0411c2312034df822fbc9adb93b7784d6ef390c"},{"version":"1.0.0-beta2","sha1":"ee6f38a92334a31a47a447b8aa663447a580a19b"},{"version":"1.0.0-rc1","sha1":"65e8e86fb01871248079732d902e210faafc4eaa"},{"version":"1.0.0","sha1":"827cd6a774b844705fb0c5e317625bfa61357cb8"},{"version":"1.1.0-alpha1","sha1":"5459e3db8e89ffbcf5e77e9cc152ae386a1b09ce"},{"version":"1.1.0-alpha2","sha1":"9fd204c114199b919fdd500ed76251f4d7ebf057"},{"version":"1.1.0-alpha3","sha1":"cc51a13c4911de253d4eabe97ac2773e82cc15e5"},{"version":"1.1.0-beta1","sha1":"a51b68cdc3ff77e89bccaa4d578d31033ec47e58"},{"version":"1.1.0-beta2","sha1":"5bf6ac729dba267b15ed52cc99096d21e5fa04df"},{"version":"1.1.0-beta3","sha1":"29f7d9cd61602c8b461320478d80e4a7600a51f"},{"version":"1.1.0-rc1","sha1":"92451c35b24440118a45f4878c28a401ae3d68ad"},{"version":"1.1.0","sha1":"88318222e4cc20a0d79fbb948e06f0c8a5d26f8"},{"version":"1.1.1-rc1","sha1":"e46f0c9228f4d4057e8788887553bab9cb48d055"},{"version":"1.1.1","sha1":"b9a46912f875103a2593934f848f8122cf04b942"}]},{"package":"support-db","versions":[{"version":"1.0.0-alpha1","sha1":"6c2c4a12774b3b1fbfb60d6eaffddbf057dc17e6"},{"version":"1.0.0-alpha2","sha1":"8a51e23b18fb95b8c0d9c512dc170fbdb4633fc9"},{"version":"1.0.0-alpha3","sha1":"2c9519312724c4c965888d9e6a41f3592b745af3"},{"version":"1.0.0-alpha4","sha1":"a62745ea1c398a218b5b770ec7b4ecb9ba53ff1b"},{"version":"1.0.0-alpha5","sha1":"5e43f83f367578008c059ee1cde0b4e0b331bccb"},{"version":"1.0.0-alpha6","sha1":"212d3c3b41fd5c333caa39e8968d4c0cb965af49"},{"version":"1.0.0-alpha7","sha1":"5ceb0120cbeb812ba11a29b11114c1688c9abe8d"},{"version":"1.0.0-alpha8","sha1":"13c740f50db9441edf74da8a1b9c7350bcd61769"},{"version":"1.0.0-alpha9","sha1":"107253e2bcaec7c613bca42f47a4311521be6d6"}]},{"package":"migration","versions":[{"version":"1.0.0-alpha1","sha1":"f5fd2978ca5e8751acee9c720cbd0147ecbf1d60"},{"version":"1.0.0-alpha2","sha1":"13c15a8be65983e0a2dc52aeff20c1070798f815"},{"version":"1.0.0-alpha3","sha1":"3b40e873b902e8f749ea6c415d6ad9a65c9433b0"},{"version":"1.0.0-alpha4","sha1":"3c2b774e06b1dc16dd38b111aac85c618f9a15d2"},{"version":"1.0.0-alpha5","sha1":"de15daf63184c9ee38e59be52ba82d15b6d59d88"},{"version":"1.0.0-alpha6","sha1":"4b29d19798f216c346a42c251668c4c6b6819ce5"},{"version":"1.0.0-alpha7","sha1":"7a78b3d9fff725d335bbb81e9b621123e47392ed"},{"version":"1.0.0-alpha8","sha1":"6d0cfc444132380ab3da4e12c23dda4f178c6c23"},{"version":"1.0.0-alpha9","sha1":"e70ba6dcace1328f9e79ea4c85facac01c023eea"},{"version":"1.0.0-alpha9-1","sha1":"f15118c72b2d142edbcc8a44cea663c2932abd9e"},{"version":"1.0.0-beta1","sha1":"6edb44f1060c21d9621fbebe77a0894690fb440"},{"version":"1.0.0-beta2","sha1":"b4698484a8ebc2a58e2a991e35038dc6ecc872f3"},{"version":"1.0.0-rc1","sha1":"c5b93446c6269af1abd9c9633d18ac622a2a2efb"},{"version":"1.0.0","sha1":"58a0e2033900e60be5113bc74617fbdd7e3e34e6"},{"version":"1.1.0-alpha1","sha1":"f3fc51a85faf64ddb03d7b62a30af6208557c095"},{"version":"1.1.0-alpha2","sha1":"b24de49ea81d472cc77c23a5ecc3ef80026c2f8e"},{"version":"1.1.0-alpha3","sha1":"45faf6bfb9841b16f0c5f48a4a7b73a5fddad2aa"},{"version":"1.1.0-beta1","sha1":"3328d85af36d3779e670d868777b09160a6323f4"},{"version":"1.1.0-beta2","sha1":"f9d775e72cc19fa71a05e969caf93888fd1af762"},{"version":"1.1.0-beta3","sha1":"c04923dbf307037cdabf545ae1030b0ec7b6dfc1"},{"version":"1.1.0-rc1","sha1":"a627a3b0bcbc49d2add6d668b3ecf151476992bb"},{"version":"1.1.0","sha1":"4b00ca71ba10fb4bb956e17dc80602926c1b9023"},{"version":"1.1.1-rc1","sha1":"adb1f508f5017adb2c8534efec4a4666836eb24e"},{"version":"1.1.1","sha1":"663d64aaa09566ad57d8367c38bd82b810674782"}]},{"package":"rxjava2","versions":[{"version":"1.0.0-alpha1","sha1":"7b3355122ef69f290be72b0a91f06b26313085ee"},{"version":"1.0.0-alpha2","sha1":"ea2d30db591a5543ade0d93c7591e7b71ae893a5"},{"version":"1.0.0-alpha3","sha1":"e041dedab7befadbb15a2fc6eeff3e67ec843802"},{"version":"1.0.0-alpha4","sha1":"eec5c3f9e51d16c1851e82d1e9f0f389d11bbc36"},{"version":"1.0.0-alpha5","sha1":"38a5df7954e1152b82c0bd4a3fb3bfef7e1c56cd"},{"version":"1.0.0-alpha6","sha1":"55bc387c52d6a220180f5df42fbb8491eb5b7a21"},{"version":"1.0.0-alpha7","sha1":"8b56fe6c933e419baf5e344fa3d9b8e32ae69bd5"},{"version":"1.0.0-alpha8","sha1":"174c591d5a09ad3a330b84e813ee1585b751e109"},{"version":"1.0.0-alpha9","sha1":"6e8e86a477cd763979949d2d6c4161ec4ee543d"},{"version":"1.0.0-alpha9-1","sha1":"a7f9beb93cb9cb38d772ae5a80ff0e293bd38e7a"},{"version":"1.0.0-beta1","sha1":"a2e2256dcf772b0eaba7b32c00a8725cce723d65"},{"version":"1.0.0-beta2","sha1":"2f5b95fe26b482704821858d4ba574d38e5c9cd6"},{"version":"1.0.0-rc1","sha1":"7f0cbd2e9f135c107ef46e2784f3542b037220e1"},{"version":"1.0.0","sha1":"c6f5de941641f7e9c7a0b9b53d339ca4af659dfe"},{"version":"1.1.0-alpha1","sha1":"16fe3d7c8927c15d23f7fed6283abccf19894e7a"},{"version":"1.1.0-alpha2","sha1":"b26a5d9f0db8672139f27f438c7a1b04757a8699"},{"version":"1.1.0-alpha3","sha1":"dc7a81b0ca5aa713ab3365b62dbcf694f1573935"},{"version":"1.1.0-beta1","sha1":"a5d50caec7b69324872416c2332c5c85aa69028"},{"version":"1.1.0-beta2","sha1":"be088a0206afb54bd772277dd9bf4e7609ee8416"},{"version":"1.1.0-beta3","sha1":"1514ac986ed763738baa142461765ccc54a4cfb3"},{"version":"1.1.0-rc1","sha1":"9f70bc6400cae1470a3e5b378cfd5dea8b58b773"},{"version":"1.1.0","sha1":"82281673585b4c27d2b77574137877f77e5a8ffe"},{"version":"1.1.1-rc1","sha1":"664343f129bf8a70509f8c71985d52f3483eca30"},{"version":"1.1.1","sha1":"8a1f9e3c3076660ba68cb57bca56bd03c1b0a26b"}]},{"package":"testing","versions":[{"version":"1.0.0-alpha1","sha1":"6b8f45103864282fd3119c394e8f2bed8f8c8095"},{"version":"1.0.0-alpha2","sha1":"dedf10efcbaa7bc6b8d703b79810595ad414d0ac"},{"version":"1.0.0-alpha3","sha1":"bf95fde847d1a0ab16774170f211fb75a55f2a5f"},{"version":"1.0.0-alpha4","sha1":"42fa414d81b9115e7c5a6fbdab7750cea24ed1f"},{"version":"1.0.0-alpha5","sha1":"caf0b9d44b04dc634eb024ad668075641775ddd3"},{"version":"1.0.0-alpha6","sha1":"d9945221737893dccc0df39e4460a0f4033fa292"},{"version":"1.0.0-alpha7","sha1":"5710b63deb919eba71ff50d34be514340e4ccaa2"},{"version":"1.0.0-alpha8","sha1":"5ec7b58d797574587390b0fa4cda80b3082219f1"},{"version":"1.0.0-alpha9","sha1":"2ccde7cbfc673a94d346032081fa5ed490342732"},{"version":"1.0.0-alpha9-1","sha1":"d4e9d13706bdef81dce500defafddb3ebc1ec315"},{"version":"1.0.0-beta1","sha1":"8e9a1c939f6e499dbd2558e2426517345c8cc091"},{"version":"1.0.0-beta2","sha1":"25f3c8555e3c7baef82f934d96b76ad740caddc4"},{"version":"1.0.0-rc1","sha1":"afc84b297d617b8b9298cd50c8fbebcb0b19977d"},{"version":"1.0.0","sha1":"7f9f7152d5cea32803e71d8d51de6661a9b4ada"},{"version":"1.1.0-alpha1","sha1":"21cd1f1a9c5c32cbe6f06588e99894cac5958be8"},{"version":"1.1.0-alpha2","sha1":"a8ad96c5755697d751cbbc93e50501be2780ef47"},{"version":"1.1.0-alpha3","sha1":"ef3e36d0a107dd8d7fe5a99ada841be48f03f5f4"},{"version":"1.1.0-beta1","sha1":"3421f81795891d688c3cde2fa87c62c469944ee8"},{"version":"1.1.0-beta2","sha1":"8909a1f9fe384d2c718916b9948467b147de7eb4"},{"version":"1.1.0-beta3","sha1":"67c5697937d6f6d0b0d70d520093531d3d05557d"},{"version":"1.1.0-rc1","sha1":"2968f54d1f73ffea98a2a5075444f2e252f715bf"},{"version":"1.1.0","sha1":"2df5ac6276f887fdb5bdd89831dfd87e7f2c66d4"},{"version":"1.1.1-rc1","sha1":"cf693fff2118b8e843d03a1add7acb5ac99c8e3"},{"version":"1.1.1","sha1":"687122b37d4690343f9e05faaf02231215ca0416"}]},{"package":"common","versions":[{"version":"1.0.0-alpha1","sha1":"4fb1be8519a8e9d931945beaee734dd7c1691462"},{"version":"1.0.0-alpha2","sha1":"29f2e15f5bf227b3c46b73defc4df8dfeb8abdc6"},{"version":"1.0.0-alpha3","sha1":"f2cae03913b7d56a18f4266d0b1542fec97c6c22"},{"version":"1.0.0-alpha4","sha1":"c852cae258317e1693cd157b9f3af01b3f89e394"},{"version":"1.0.0-alpha5","sha1":"da7c4f373e9a9e114fc3a12edcbb7bd7f8822b4b"},{"version":"1.0.0-alpha6","sha1":"e165a7b49ca1006dd4cc5ed8e6b5531ea26a74d7"},{"version":"1.0.0-alpha7","sha1":"1c551799ccc16763e2657db156508201bb9a450e"},{"version":"1.0.0-alpha8","sha1":"b7a0cef13fa23defbdbdf45b37638ce217f6692e"},{"version":"1.0.0-alpha9","sha1":"863651d5dcd5d28bd43475d2a3e6406c11cc4462"},{"version":"1.0.0-alpha9-1","sha1":"9c06db25a3274ddeafff6a1dbc65a63df7988368"},{"version":"1.0.0-beta1","sha1":"d8fb512c7fe3c8e4c1c6970369fe4784aa43d021"},{"version":"1.0.0-beta2","sha1":"ca1ff4dcde3382ff37a9a99d91b0bc49df7fd6f"},{"version":"1.0.0-rc1","sha1":"6d39ccad7bcc6647ef820bf940e3b18774d560a4"},{"version":"1.0.0","sha1":"1cbd3ddc566125293b13af5c0a65bc015476e83c"},{"version":"1.1.0-alpha1","sha1":"30d0b636d97218242fc2f48d2c6ec1e3660c4fa8"},{"version":"1.1.0-alpha2","sha1":"2a84667ff550e9b84c80363c0e2f807afca4d177"},{"version":"1.1.0-alpha3","sha1":"ef21c04282507651a00ea8220d9f108fb4d81cf4"},{"version":"1.1.0-beta1","sha1":"1180040c977ec2d20ee45e619f5331a790eff45f"},{"version":"1.1.0-beta2","sha1":"95058f00465fa35ae8f50ccf578d101ba0495a11"},{"version":"1.1.0-beta3","sha1":"ba686120c56bd6d982e0a99fe6f17dc47627ab6f"},{"version":"1.1.0-rc1","sha1":"10ac1a3b43900dec8adf989cbee964c891475fa8"},{"version":"1.1.0","sha1":"6e749dde07b1f6fe13444c94befde3c28e700d5b"},{"version":"1.1.1-rc1","sha1":"5397b78fb7e122920aa14fe84062aa344e9d8fab"},{"version":"1.1.1","sha1":"8094cc50760b9ac665a8ea76b51888f70042e2b5"}]},{"package":"db","versions":[{"version":"1.0.0-alpha9-1","sha1":"8f4bc881aae7a056bbea63453be669c830912939"}]},{"package":"db-impl","versions":[{"version":"1.0.0-alpha9-1","sha1":"5e08b75af92757a0a08aef6d38cced6e881bc57a"}]},{"package":"guava","versions":[{"version":"1.1.0-alpha2","sha1":"e9ebc2f2889ac2dde3bee5f8b020c8133fe90329"},{"version":"1.1.0-alpha3","sha1":"8ee1647b497054405cf9fed2f1571023970d7b5f"},{"version":"1.1.0-beta1","sha1":"b28f323a190866034f39ea3e53c2d09ef532dc4"},{"version":"1.1.0-beta2","sha1":"94a054c4524b548d628966b9f44c2a15f3257ed9"},{"version":"1.1.0-beta3","sha1":"743cb6bf77561aa8688c6d6b644b533f9bbeefb"},{"version":"1.1.0-rc1","sha1":"1be0f1f205879c56a0d10743bf9af225f3919db7"},{"version":"1.1.0","sha1":"274c415b339fe19041dfa3475ac55034357008bb"},{"version":"1.1.1-rc1","sha1":"3339710111cda5c457cbce0c51367969065d3ff1"},{"version":"1.1.1","sha1":"6676b30d4d7a7a862b7e25ae34bb28c75301e33e"}]}]},{"group":"android.arch.lifecycle","update_time":-1,"packages":[{"package":"compiler","versions":[{"version":"1.0.0-alpha1","sha1":"da47bd0aa5c0bc2255527efcb2613ec5232e3d4e"},{"version":"1.0.0-alpha2","sha1":"e4a1bf6db6023ecec731f2fba3eb3f2ec70e1458"},{"version":"1.0.0-alpha3","sha1":"ab76889418668b9d4725a8378169f1d4687ee607"},{"version":"1.0.0-alpha4","sha1":"a7540567c3e9cb51fa01fc9e7fcb7dfd4d2abfa8"},{"version":"1.0.0-alpha5","sha1":"dda83efdb8180371c72e6bb2adfb870d72ee6e41"},{"version":"1.0.0-alpha6","sha1":"6031c67d02f7491abd7610635e01d3d02937caca"},{"version":"1.0.0-alpha7","sha1":"8a5654c0deb231c3f3bd27a15ec1895706147a2b"},{"version":"1.0.0-alpha8","sha1":"ba0e89cecad204fc7cf44c8b48d993f810b3d699"},{"version":"1.0.0-alpha9","sha1":"b86966ee246c5130a4e74fd1edd99a1d60bba262"},{"version":"1.0.0-alpha9-1","sha1":"e523b9fc6247e9f2c4723427e6c7020551fe64a1"},{"version":"1.0.0-beta1","sha1":"25add53aed1c00c29bbf17c05060a111708217b5"},{"version":"1.0.0-beta2","sha1":"c49e171df30c3f605b3b572b6375b05f27741d3c"},{"version":"1.0.0-rc1","sha1":"3254c267ab91bd6a39520d8aa5dc268c94588bf2"},{"version":"1.0.0","sha1":"e835299bec50869877ad2315edb2aa9b8c9f8683"},{"version":"1.1.0","sha1":"1d78ed7342dc51332ea8823f48833783320d3df9"},{"version":"1.1.1","sha1":"96ab955a10609f6c3d91b872628ccf44c567a22a"}]},{"package":"runtime","versions":[{"version":"1.0.0-alpha1","sha1":"c0b8b60bfa7eb673e2216fa5e28c9356b823759c"},{"version":"1.0.0-alpha2","sha1":"e05369db9f771038b890824c9339ccc8a907c5b5"},{"version":"1.0.0-alpha3","sha1":"2332117e7b768cf41c5874bfb9d6aac2e71963ff"},{"version":"1.0.0-alpha4","sha1":"4a970a7f2f47e8a557a857fe4bc21e9ea7db910c"},{"version":"1.0.0-alpha5","sha1":"9350ed60ffbd7fa0fb5bd483f4d6e421f147d7ba"},{"version":"1.0.0-alpha6","sha1":"2cd119d5e21692a2f07eb1106d48b3f2e9631f55"},{"version":"1.0.0-alpha7","sha1":"32d47f04beefb93f37a4f819374335f7fc07ec83"},{"version":"1.0.0-alpha8","sha1":"ccd4ed611a1395d366a2fedc21c3c53271238504"},{"version":"1.0.0-alpha9","sha1":"fae603bf99e7be16405e6a9efb3c8b3c4ec25062"},{"version":"1.0.0","sha1":"30c60a8a357ee1321ffd0c9f08ef54b24045cd10"},{"version":"1.0.3","sha1":"adfa3b2a02d40c3e4c8e81e2f776c4fe39eb8c96"},{"version":"1.1.0","sha1":"95428e5f6bf6875a6a1125d2157c3836a1a837f5"},{"version":"1.1.1","sha1":"4286e1ae9364b485cb2ff7a370e355b7c570015b"}]},{"package":"extensions","versions":[{"version":"1.0.0-alpha1","sha1":"d4d16e97e77238da295816176439fa8effb9e366"},{"version":"1.0.0-alpha2","sha1":"2bd4f5c47895467bdcb031f184f2f779be648270"},{"version":"1.0.0-alpha3","sha1":"71f7c486644eab44655b995ae0961bc42d5558fb"},{"version":"1.0.0-alpha4","sha1":"bbdad61612129aef59b317da03ff34d2c95e3f01"},{"version":"1.0.0-alpha5","sha1":"e01cf4d94949e7c5617bbc337025a764557ecb1e"},{"version":"1.0.0-alpha6","sha1":"605909db7d1995a9a1357e47b77a0e9753a80336"},{"version":"1.0.0-alpha7","sha1":"929ca6c8146a928609c43edf198da64e0c66f69f"},{"version":"1.0.0-alpha8","sha1":"84d58221e216dd691c718d8312e920acb242231e"},{"version":"1.0.0-alpha9","sha1":"3d27d62b6ab03f61ae3b8b64795eb2fc10ad4044"},{"version":"1.0.0-alpha9-1","sha1":"24641fd197d93536d01fdadd98da3e6888cc3fb8"},{"version":"1.0.0-beta1","sha1":"3650843e53eecbc3464a6a13a9d9384750b56f1e"},{"version":"1.0.0-beta2","sha1":"17e9dccb3f6c9d8c671c54507b80292b38cbd0bc"},{"version":"1.0.0-rc1","sha1":"156831c85b4ac25b17998ffd450192e4735e13a5"},{"version":"1.0.0","sha1":"e74bb83128b454bbe3c4d5f3944cc55ba37dd453"},{"version":"1.1.0","sha1":"3109d7399a427fca470fd6ab0e5518794c7fa954"},{"version":"1.1.1","sha1":"235927c81fcac6031552a04b7daf657bbfaaa3d"}]},{"package":"reactivestreams","versions":[{"version":"1.0.0-alpha1","sha1":"274109c11f32e23e7642092f47bd5fa0a775e1f4"},{"version":"1.0.0-alpha2","sha1":"836d53183ea01a6886d3e0be40d168f1d213fdb7"},{"version":"1.0.0-alpha3","sha1":"5c48e0ad9b306833f1e709db09aa4afb67b3c0cc"},{"version":"1.0.0-alpha4","sha1":"613b7fd33b36d50a9906b04040b0b4e7199504e"},{"version":"1.0.0-alpha5","sha1":"1a8f977401fae62488ebeea42fa92c97b6c9d08a"},{"version":"1.0.0-alpha6","sha1":"62e703f9fa947a9e379e3bc6cda3f72b013e207d"},{"version":"1.0.0-alpha7","sha1":"ddea6277845e397c361a17640109a97cdde2f39f"},{"version":"1.0.0-alpha8","sha1":"1deb125246c6cef7c992f25277450cc86717c1ad"},{"version":"1.0.0-alpha9","sha1":"50f61b2f9c0af8e12ad5b5adabe963c087d6c0c1"},{"version":"1.0.0-alpha9-1","sha1":"f3e5ead43ce29e3b78ab02e712e9426e730a5ab8"},{"version":"1.0.0-beta1","sha1":"363601c2fdcbe0900e51cd09e48a88e482c139b9"},{"version":"1.0.0-beta2","sha1":"91823c93b606a73e5dc4631e24a7a5ab5879b915"},{"version":"1.0.0-rc1","sha1":"69ca1a0bde516aea292c3c3e2d62efb0d46f6a0d"},{"version":"1.0.0","sha1":"2e616d0cdbdbcf4e0a8a603cc7f827d029421111"},{"version":"1.1.0","sha1":"880860fd98b1f4eeb989eb3e27ba6ad7cd3dbf57"},{"version":"1.1.1","sha1":"23dc314941f1d48ae7216d8272cf8c89840b5c8c"}]},{"package":"common","versions":[{"version":"1.0.0-alpha1","sha1":"dddb0f589fc65ed3a262d1a90f4d61af6891776a"},{"version":"1.0.0-alpha2","sha1":"3127d4c5a7b7f6987ecd3923b43f6a71b860091c"},{"version":"1.0.0-alpha3","sha1":"ee266d06dc5e9a72af1fbeb36431a5e6e11ee303"},{"version":"1.0.0-alpha4","sha1":"4fd137d8a0a1fe39294bf30131b0c3c8f452d8c"},{"version":"1.0.0-alpha5","sha1":"16bbceb3231045909d9b2d19758c57a30831ce9f"},{"version":"1.0.0-alpha6","sha1":"ef2c8e5d805f76ed3f3760ba2aeca2fb10c0c789"},{"version":"1.0.0-alpha7","sha1":"c4627490cc3157b1fc28a025b29fda49b3696ba8"},{"version":"1.0.0-alpha8","sha1":"a90cf14b188c4223c980c83784335e30d3bb89f4"},{"version":"1.0.0-alpha9","sha1":"1e6fdde624d785fa1e04f2db8d8a6520b573758"},{"version":"1.0.0","sha1":"e414a4cb28434e25c4f6aa71426eb20cf4874ae9"},{"version":"1.0.1","sha1":"5269157f5f2c8ae81bf792bbe98bf4541b758488"},{"version":"1.0.2","sha1":"3cf2ddb6fd27236f2c81ea160f416867ae68e638"},{"version":"1.0.3","sha1":"7d7f60c4783872861222166f6164215f8951c7b1"},{"version":"1.1.0","sha1":"edf3f7bfb84a7521d0599efa3b0113a0ee90f85"},{"version":"1.1.1","sha1":"207a6efae6a3555e326de41f76bdadd9a239cbce"}]},{"package":"common-java8","versions":[{"version":"1.0.0-beta1","sha1":"ca201752c6c20b7a3081aa70617c46210ef568d9"},{"version":"1.0.0-beta2","sha1":"f87465807d2a62c2470acab236778db3d30d3ba5"},{"version":"1.0.0-rc1","sha1":"89ff97ccf118b471fc3cf55cf6c32720451059e8"},{"version":"1.0.0","sha1":"a7458929c2f84a8a59b6b4da631a36f07091cf79"},{"version":"1.1.0","sha1":"38a98f7e060beb4ac1c3b2caf5e7c7a3b2225f5a"},{"version":"1.1.1","sha1":"795d68cd761d093ccb235d1d91b8fd17c2ae25ff"}]},{"package":"viewmodel","versions":[{"version":"1.1.0","sha1":"74ee369b874da61b5b81bebd7b8df0c2577309c8"},{"version":"1.1.1","sha1":"897b6e22c8357b23ab0c7600c961549c098f5ccf"}]},{"package":"livedata-core","versions":[{"version":"1.1.0","sha1":"d6827a080a137fa345a0ed4a438e14e5633e0032"},{"version":"1.1.1","sha1":"30ede25cb577323f039c2e3d72b3b56526a2b2e6"}]},{"package":"livedata","versions":[{"version":"1.1.0","sha1":"f2c295695ab5b9b087d22424f1dc378f5566bce0"},{"version":"1.1.1","sha1":"82e0b1bf2dc8ce23898cf433cc150df7b3dba952"}]}]},{"group":"android.arch.core","update_time":-1,"packages":[{"package":"core-testing","versions":[{"version":"1.0.0-alpha1","sha1":"c1f1710690d0498aa4e7250d9f03a7c3be13b1c7"},{"version":"1.0.0-alpha2","sha1":"90b6e0922f21cb4507502aeb0311f13669da1952"},{"version":"1.0.0-alpha3","sha1":"c8a26ae6abe628bdeef51da25abec11a5e5e188c"},{"version":"1.0.0-alpha4","sha1":"7ea2040910d1b507349981ae4785db75fc11a3de"},{"version":"1.0.0-alpha5","sha1":"931b5b275c7df226a7c5d89bcb0ae06ab1deddef"},{"version":"1.0.0-alpha6","sha1":"3ac5f3ce83a13bc2a49d50f3523c6fba8acfd121"},{"version":"1.0.0-alpha7","sha1":"13eeae380ca37bef1257bd83ea4271cae28e5ccf"},{"version":"1.0.0-alpha8","sha1":"9562053a671148621829ef01b693e16033274ea7"},{"version":"1.0.0-alpha9","sha1":"866a5422b5bb487b1b96b1e9ff935e8a198b9fd3"},{"version":"1.0.0-alpha9-1","sha1":"5a601d01eb2206b0ad0f8a5401d3e35ea3f24aa8"},{"version":"1.0.0-beta1","sha1":"506fe75f7760e8500fe682a5183402f483c3a879"},{"version":"1.0.0-beta2","sha1":"abdc8bdc8be18079011c4d39eb3ef1f0f3b7ed66"},{"version":"1.0.0-rc1","sha1":"dcc1c3c6a4e92065f9959fd0c4f759fb383e11f4"},{"version":"1.0.0","sha1":"8d139aaf376b9626da0f0a57f7a7db92d7a7c11f"},{"version":"1.1.0","sha1":"6cb393c80a4f5ffa38c7317d087608e636be9e31"},{"version":"1.1.1","sha1":"95fc8aa1d8b5ac4e5feeb1e25ee914626bb91918"}]},{"package":"core","versions":[{"version":"1.0.0-alpha1","sha1":"e675a5ec0a735ff3bddc0352b6bfea0d53f6fce4"},{"version":"1.0.0-alpha2","sha1":"80290486b8f5d532b36ac2bf97c0004f6949fadc"},{"version":"1.0.0-alpha3","sha1":"c2ab187fe0ef960a44e492ce6f9f4b43592658fb"}]},{"package":"runtime","versions":[{"version":"1.0.0-alpha4","sha1":"c79ce8af7e7abac63063c2cbd9662270c4c25c02"},{"version":"1.0.0-alpha5","sha1":"351f2236dd9c2b7f535048b40c13b2973068e08e"},{"version":"1.0.0-alpha6","sha1":"ddeac805c3863b9ffd4d3d65b20a282118bc0282"},{"version":"1.0.0-alpha7","sha1":"fcdbc3f389d9ab397a45c7fee7493ebfdeb3ef86"},{"version":"1.0.0-alpha8","sha1":"888b5684f867e7ff99c6137fb28b8dde519a15ea"},{"version":"1.0.0-alpha9","sha1":"62eba51414a62bae0d90d5a6b42fa327ddd28b61"},{"version":"1.0.0-alpha9-1","sha1":"a3b0ebe602bfc86104fe5bd11738ec2190dc496f"},{"version":"1.0.0-beta1","sha1":"6221c98ebcb69a5c995d532927ba852492d6f3d"},{"version":"1.0.0-beta2","sha1":"3e762dff9082725343a634fedbd1a14439cb61e0"},{"version":"1.0.0-rc1","sha1":"57a53ad3b0037a3d59beca8ebe7b9d2e27035d01"},{"version":"1.0.0","sha1":"9d0c7c87fb383b40561e0ac5e43b239cfea61b43"},{"version":"1.1.0","sha1":"dabc0064464782c5014c7e8be5e93bd76466c41"},{"version":"1.1.1","sha1":"a7e27caf787e14c0d8417be907f4a31f0306acb2"}]},{"package":"common","versions":[{"version":"1.0.0-alpha4","sha1":"576aad6968dcec427e61e84299bea026fafc4f97"},{"version":"1.0.0-alpha5","sha1":"ef1ec521c95486fb256b3677a95c340ce9c49407"},{"version":"1.0.0-alpha6","sha1":"9cf4fa600a2eae8987924840a1606ebc9e47f51c"},{"version":"1.0.0-alpha7","sha1":"d5e83ae9011b463d456d28dc4acfbc5e60ba19d0"},{"version":"1.0.0-alpha8","sha1":"eca55e2b62e608e3656425ef08e814b4afd8fc9d"},{"version":"1.0.0-alpha9","sha1":"e39a71ca926d563e2ed87e18ded116960194532e"},{"version":"1.0.0","sha1":"a2d487452376193fc8c103dd2b9bd5f2b1b44563"},{"version":"1.1.0","sha1":"8007981f7d7540d89cd18471b8e5dcd2b4f99167"},{"version":"1.1.1","sha1":"e55b70d1f5620db124b3e85a7f4bdc7bd48d9f95"}]}]},{"group":"com.google.android.instantapps","update_time":-1,"packages":[{"package":"instantapps","versions":[{"version":"1.0.0","sha1":"fe9e068fa2f39de0ed770a649d9aed47390a128d"},{"version":"1.1.0","sha1":"e83edc113f6330e0b0364c177019c7a2356bb4c5"}]}]},{"group":"com.google.android.instantapps.thirdpartycompat","update_time":-1,"packages":[{"package":"volleycompat","versions":[{"version":"1.0.0","sha1":"ad5ea49a4775ea2e7a1bea9530c796ed2100aed5"}]}]},{"group":"com.android.java.tools.build","update_time":-1,"packages":[{"package":"java-lib-model","versions":[{"version":"3.0.0-alpha1","sha1":"819624007f7a44ec449a15338d39babdcc237a84"},{"version":"3.3.0","sha1":"7c1699138331c4980dec2801aed7891f713667d8"},{"version":"3.3.1","sha1":"c5e0bd55cd80f5fa21529a5032addcd9258ada51"},{"version":"3.3.2","sha1":"5b7abb7cb12bca0e341f722636ba33ab55e1e1c"},{"version":"3.4.0-alpha06","sha1":"eafd88485c7a52200615d971c6f1439c47eac73f"},{"version":"3.4.0-alpha07","sha1":"bbe69cf6c8de96ea2aa4cda6f34e05af8bc845d9"},{"version":"3.4.0-alpha08","sha1":"35cfaeeb8b56ebf9c759dc7a762c58f7134fc884"},{"version":"3.4.0-alpha09","sha1":"eadf806af4e117f320b3db1b6a26e651278a4ea1"},{"version":"3.4.0-alpha10","sha1":"9fea7a0362093e7bd9fda60d88420582e1e6e9d5"},{"version":"3.4.0-beta01","sha1":"3c89c28db0d4111d24f5602c949ae32b42e1bcdc"},{"version":"3.4.0-beta02","sha1":"3d08c2b3926f8f7a825b4637864c127d91f3963b"},{"version":"3.4.0-beta03","sha1":"e6064bad9a7dbafe55007c3901c144b10d5414a0"},{"version":"3.4.0-beta04","sha1":"d9d3283bcca82f43d35c3c9f049ad7e87e3fa2e6"},{"version":"3.4.0-beta05","sha1":"901b65ec81082b6447d06a8636e494b37c3cf2ea"},{"version":"3.4.0-rc01","sha1":"e58fc0e3462b2baedc133703011096d09c600ab9"},{"version":"3.4.0-rc02","sha1":"d91bd90de0a5a277d93870f15d2fc803eda9413e"},{"version":"3.4.0-rc03","sha1":"cfef7e52c050eb98ac0ccb0b143b8fa283b65f14"},{"version":"3.4.0","sha1":"270c1969f4e74b69a38e828ca6c22816da97dff6"},{"version":"3.4.1","sha1":"f06626ca5201dd08ac6933714b3dde09fbb09614"},{"version":"3.4.2","sha1":"10e2414ab08eb52c9533b8b74549335bf98549a5"},{"version":"3.5.0-alpha01","sha1":"5abb45ca664e11f70809449c91c4c6538f274338"},{"version":"3.5.0-alpha02","sha1":"3b0f8e6cbef22b978018c2b8785162ca64a03fba"},{"version":"3.5.0-alpha03","sha1":"228bb39003852a995059a96a4ff30409d211a881"},{"version":"3.5.0-alpha04","sha1":"909e957f8c5408417ce096234ed3bed6ce0aae2b"},{"version":"3.5.0-alpha05","sha1":"ca591bb64bf6fccbab9e0b1e33b7691c44c1bb6d"},{"version":"3.5.0-alpha06","sha1":"590bd2d870902ada092f2ad5f09eac8276de7659"},{"version":"3.5.0-alpha07","sha1":"ff07579f0ee88b537618ae4a8ace0e952392a5bb"},{"version":"3.5.0-alpha08","sha1":"27734e458e63387d607ca4cfa8ea59cdc544ebdf"},{"version":"3.5.0-alpha09","sha1":"8bf44cd5be5a4361bd7e8b1c0128376e2f5eee6b"},{"version":"3.5.0-alpha10","sha1":"1558c63677cc9488f0c4e7fd9fa45b4416f79d3e"},{"version":"3.5.0-alpha11","sha1":"4e15ee44d0136f8317d71eb6690da6835b6dc7c0"},{"version":"3.5.0-alpha12","sha1":"95deca1eecee54fdde11498658cf6671cab0243c"},{"version":"3.5.0-alpha13","sha1":"3afd8c3de61921f49fb94255e9c5d6ecd7622735"},{"version":"3.5.0-beta01","sha1":"1adad0a39d77751b468f87d3466c4ce7e02404b2"},{"version":"3.5.0-beta02","sha1":"8a7724ee18bfb65fb9a06a2ee5cb0c3f56b96296"},{"version":"3.5.0-beta03","sha1":"d43803028a34faa8694e2d3d5b91c80d995fbcce"},{"version":"3.5.0-beta04","sha1":"a24cd9bcf3d5004029778dd5a04524dc8c7817e7"},{"version":"3.5.0-beta05","sha1":"b77758bf3801f8576c1c08b42e9f3eb1ec100c5e"},{"version":"3.6.0-alpha01","sha1":"18072d1cbc697824225e56aa31663d046d9aaa42"},{"version":"3.6.0-alpha02","sha1":"6194999bcfee854f28c28e5954755ea513a0f446"},{"version":"3.6.0-alpha03","sha1":"6da3bd5138c925e3dedb82565dc049e8a710f406"},{"version":"3.6.0-alpha04","sha1":"bf67207f47e5509e8544b4fce4c8a014625458dc"}]},{"package":"java-lib-model-builder","versions":[{"version":"3.0.0-alpha1","sha1":"8ccfe45de780df525abcddbf205257346cf4a745"},{"version":"3.3.0","sha1":"b4a23c56f1b31d1829586caae85238b25995a72b"},{"version":"3.3.1","sha1":"32b21a97a2be043b1f2c480c92de1eb176135b39"},{"version":"3.3.2","sha1":"219357a0ce6c437304f67bf125fe735678f8773b"},{"version":"3.4.0-alpha06","sha1":"ed113d2349305525018f61421d33b60988858bbf"},{"version":"3.4.0-alpha07","sha1":"9a0e26a04a30af9ca611d3b07506cab7d26aced1"},{"version":"3.4.0-alpha08","sha1":"b9ef76cd8689c7e2c57d8dcca6db407742ca776a"},{"version":"3.4.0-alpha09","sha1":"d9aae183f4834da4c79aefeb891bb78edf5d0f27"},{"version":"3.4.0-alpha10","sha1":"61514fa3748e0472f98c70df2babf7d7457bb0ed"},{"version":"3.4.0-beta01","sha1":"f7a2e9b8c92c938dfddc7d8ec17f7d6280bd13af"},{"version":"3.4.0-beta02","sha1":"e4af1ecfd3da6dae2dfe496bc3bf191064d3a61d"},{"version":"3.4.0-beta03","sha1":"505225c6a8a714f6cb3e723f0007f0b13990d373"},{"version":"3.4.0-beta04","sha1":"fdffdbc334677e27508b676e0475ed817fde16b2"},{"version":"3.4.0-beta05","sha1":"388e24b8b79dc0be2d8e745281d74c5a22a5727b"},{"version":"3.4.0-rc01","sha1":"fed864a25447c54d77acf18a6514348da31dda2b"},{"version":"3.4.0-rc02","sha1":"baef30c86f9003b0e042c1dc734bac354745347e"},{"version":"3.4.0-rc03","sha1":"a134b849bddf4a5370b1c120656f3b5e1e1b98ce"},{"version":"3.4.0","sha1":"cf109a55409510ade3a01bba21567aebe8949e0f"},{"version":"3.4.1","sha1":"5bbe7f8e0f86bb88397831963fcf0e95a6d9fec6"},{"version":"3.4.2","sha1":"4fa127cf1196151fad710125c773782559c6ca9"},{"version":"3.5.0-alpha01","sha1":"b500bfb0fa5c8ca99cf8775a2d559b4eace1c126"},{"version":"3.5.0-alpha02","sha1":"42b15d1e192c441cda19d4f098e75c7966ea0751"},{"version":"3.5.0-alpha03","sha1":"5d6a3f882fcdd20b37a39bfdb5f791de7a0db345"},{"version":"3.5.0-alpha04","sha1":"800f80dde2be5195d0222387ee9ce92b04b41a8"},{"version":"3.5.0-alpha05","sha1":"e2e774285c2fbde41353f528f3744f5378857a4d"},{"version":"3.5.0-alpha06","sha1":"107d3d21e6dc1bd4da7485bf6a7ce242bb427e"},{"version":"3.5.0-alpha07","sha1":"491f2278806eea3ceb828b11fce86520eadac810"},{"version":"3.5.0-alpha08","sha1":"743a03fd4ab6e08f1bb16f8bd2a5e0085e0acf60"},{"version":"3.5.0-alpha09","sha1":"fe85084c7a50926e878b1a9647f66b8ce1ce8715"},{"version":"3.5.0-alpha10","sha1":"709f2ddf3810d19b4108cd5aa013ef263438a833"},{"version":"3.5.0-alpha11","sha1":"4e85b4e35955380b9d15dbadeda1037231204325"},{"version":"3.5.0-alpha12","sha1":"9d413a87547c73c5104376242cdc4c85f885432a"},{"version":"3.5.0-alpha13","sha1":"48e413e95a7405afd840a027bbe946c4fff081d5"},{"version":"3.5.0-beta01","sha1":"820285f1a8165cd0dc73fefc7780d12531e7daf9"},{"version":"3.5.0-beta02","sha1":"68fa3333beb7a06dc04ecdd903bec98fb7364f7f"},{"version":"3.5.0-beta03","sha1":"4477c38d231d2c46bea190a10c1ed275b1afcb15"},{"version":"3.5.0-beta04","sha1":"4e2a5ffb68d1027724de5947b6ee562afd70fb4b"},{"version":"3.5.0-beta05","sha1":"781cd85c10ab6beef81b47ccdb2a485fdba3a255"},{"version":"3.6.0-alpha01","sha1":"6ef1e8b4eddcb8aa6eabb22b30b6184b9e2011e8"},{"version":"3.6.0-alpha02","sha1":"1711a4e373d3a05678a4247e8f75271ee1d807e3"},{"version":"3.6.0-alpha03","sha1":"28a9f3fbce03880b2a87dea6960f4d8920118471"},{"version":"3.6.0-alpha04","sha1":"d5cab4e132743c363f09e156ca649062c98155a"}]}]},{"group":"com.android.tools","update_time":-1,"packages":[{"package":"dvlib","versions":[{"version":"26.0.0-alpha1","sha1":"a4446b9069f3257d4bca1c4b222a72c7949776e1"},{"version":"26.0.0-alpha2","sha1":"586f7b73a7fd0ab414e14c7c2f8437d1161d493e"},{"version":"26.0.0-alpha3","sha1":"9ea775c07eac4dc5a8df5691ab541afe6f6ea8fc"},{"version":"26.0.0-alpha4","sha1":"321c6450ade8c291ff8dc9e1cad5e606ac335fdd"},{"version":"26.0.0-alpha5","sha1":"45837a87ec8e411963398b35960a68aa6abade2e"},{"version":"26.0.0-alpha6","sha1":"134747306196b92a3c28db5cd16d6cc36c2072e"},{"version":"26.0.0-alpha7","sha1":"a95f87e5224b054d15f6f002e991e5f7731279b1"},{"version":"26.0.0-alpha8","sha1":"43fbe7bcd998355e6d7f6729a20db95232588bec"},{"version":"26.0.0-alpha9","sha1":"fcea44a0a5cb9ac4272fd5b7eda93bda22c19028"},{"version":"26.0.0-beta1","sha1":"7ca09c14f817fd35ee643d615fb2278b2ee3cabf"},{"version":"26.0.0-beta2","sha1":"adde160bdd1862af07f43e23355c37d27e4dd1a0"},{"version":"26.0.0-beta3","sha1":"cc35c7893b343fc8d5fb1e6477ae9a9d5cd52ccb"},{"version":"26.0.0-beta4","sha1":"2c8058af59fe2496facf1db8d1320ff288db25e5"},{"version":"26.0.0-beta5","sha1":"ca55fac8b97d485cc0c4aa0ef48ae35235a9b426"},{"version":"26.0.0-beta6","sha1":"4f19f901c996d73064202b74416860bae0bd3700"},{"version":"26.0.0-beta7","sha1":"e5d0015a62bcd34493e6f627e6a59c66456f3401"},{"version":"26.0.0-rc1","sha1":"3095d604a629e8e5a9712a83cae4c4b975926f65"},{"version":"26.0.0-rc2","sha1":"8f941e5ad982b5cdc56eb1019dc51fc59d8905ad"},{"version":"26.0.0","sha1":"50d8b18ccc5233c4e7ca4c98f9708d44f80ace82"},{"version":"26.0.1","sha1":"46f3eeaf812811374b4ea2c49ad52687a7ff6a87"},{"version":"26.1.0-alpha01","sha1":"62d03316bee54dc55150a6aeba264131d6626d4"},{"version":"26.1.0-alpha02","sha1":"635f83122eb095587ed8fd08a4f7a48ce16ae9c2"},{"version":"26.1.0-alpha03","sha1":"e95c456031da25cde20349d2c17ef153b26eebc"},{"version":"26.1.0-alpha04","sha1":"4fcd36bd4074b713929ae7c055c7c388d5501120"},{"version":"26.1.0-alpha05","sha1":"2bea5c549bb4a095425a51664feddbfbf9860bad"},{"version":"26.1.0-alpha06","sha1":"4b081696552db0c30f90f21b00effedca06da84b"},{"version":"26.1.0-alpha07","sha1":"65e832890b42828309d0982357983b54ec0e3e46"},{"version":"26.1.0-alpha08","sha1":"20bb9b9c5a046b2cef7fa6249f0d5aa1d2fe6f6f"},{"version":"26.1.0-alpha09","sha1":"93e9648ad51bdbd40c4b2ee6948868e6c19625ed"},{"version":"26.1.0-beta1","sha1":"ecd6463b8438e5af2df51ade842356f5c15d1904"},{"version":"26.1.0-beta2","sha1":"49467cec79f42d25c4e95a48edb9887072c702e0"},{"version":"26.1.0-beta3","sha1":"64d101c3f56a77b8aab3176a0423021a28ee9885"},{"version":"26.1.0-beta4","sha1":"3c5626a4901ebc99b53f8ac7894bc006507f5d67"},{"version":"26.1.0-rc01","sha1":"dc93bd4e6e6ca53f36b692bf9b0631f8ad6240bb"},{"version":"26.1.0-rc02","sha1":"d743d66202828877620dcf9c760ca6ba3a3d7b11"},{"version":"26.1.0-rc03","sha1":"1e85e29169f4be5a276fb5138a575e6a99fc51f2"},{"version":"26.1.0","sha1":"eb04b3991369c83e7a15da35fe02988eec583982"},{"version":"26.1.1","sha1":"1b0a07bae1a3726b981a2c0c8110c815ef772237"},{"version":"26.1.2","sha1":"eb39925fee6e726468fc10344ec988c086301ed7"},{"version":"26.1.3","sha1":"fc4804359bbd0168fe52e746c5d455fc2afa0080"},{"version":"26.1.4","sha1":"5d443b51ad041e9599cfdb278cd2f6c9d2ccdf28"},{"version":"26.2.0-alpha01","sha1":"e5d388d7afc9b0e0020d0c930c9763a29ace0ee0"},{"version":"26.2.0-alpha02","sha1":"448f7e085a02666afd567cc798b957b5b9e10493"},{"version":"26.2.0-alpha03","sha1":"f84e01f67151f043be0ce55c050aa24655be55e3"},{"version":"26.2.0-alpha04","sha1":"4ee3beebfebbc80fb26f9c8f212bd46a8285d5e6"},{"version":"26.2.0-alpha05","sha1":"7b092c1f3e7f36bb277b5e8cd20ee4f03709d806"},{"version":"26.2.0-alpha06","sha1":"d22e8d7e248a17f995d43830b30a73a67be539c9"},{"version":"26.2.0-alpha07","sha1":"2ebe9c77e7b7e8011e5252c79b2b444487ec30dd"},{"version":"26.2.0-alpha08","sha1":"41d693938e4d66f948045cecf9824a5c6b4b0c62"},{"version":"26.2.0-alpha09","sha1":"feb3f8886115cbd13ade719f046174397042a6ef"},{"version":"26.2.0-alpha10","sha1":"3496713214e778f38b551e82763a41d1e45d7d27"},{"version":"26.2.0-alpha11","sha1":"2050ef0440e726ff0144f2e03b7d7de518280f8c"},{"version":"26.2.0-alpha12","sha1":"a3eff642f860d20f6108c3583ef5012f56007178"},{"version":"26.2.0-alpha13","sha1":"64a8d964901b1878e47cbf70262930d29b2daeac"},{"version":"26.2.0-alpha14","sha1":"449a8893a0c6e50bdec8f5b45a18322d44adfe27"},{"version":"26.2.0-alpha15","sha1":"b030b85a5c7b713783e34e07bd9928fe6d20ad56"},{"version":"26.2.0-alpha16","sha1":"57067444239aa29046931578807bd2d271c068b8"},{"version":"26.2.0-alpha17","sha1":"a77e467dc5d502b322b7923faaf44c8876491b8b"},{"version":"26.2.0-alpha18","sha1":"6c5f5448b86df137f7b229f79948dfd0c601abb3"},{"version":"26.2.0-beta01","sha1":"94aaf96c89c00ef9ca96769c55e2dd03c5416b51"},{"version":"26.2.0-beta02","sha1":"ac2872ac7a2225c95176b3339ba2bc916e2387f7"},{"version":"26.2.0-beta03","sha1":"83f29139ac8c52813f4c9317f2b4be5e25545d3"},{"version":"26.2.0-beta04","sha1":"eb8ace2359d23332422cf4abf481bdbef6e4f0e0"},{"version":"26.2.0-beta05","sha1":"d9144d45e3c4851203c13110fb2934fdbe01f559"},{"version":"26.2.0-rc01","sha1":"71108b1c8975822bdfab03f54cc666c696f87305"},{"version":"26.2.0-rc02","sha1":"80864251ba315bcf3adaef7d84714a1b9f96433"},{"version":"26.2.0-rc03","sha1":"d4bf76d89c94ab583462a793d7ef6bfad3d22e3a"},{"version":"26.2.0","sha1":"8035cb73aacceed0c34c0802dea4463064b6fc67"},{"version":"26.2.1","sha1":"fa3cc125cd311f65dcae6310e381b20d4b2ec0fe"},{"version":"26.3.0-alpha01","sha1":"c7ec42859bbe8060af795dcfdf4ebebb78816eb9"},{"version":"26.3.0-alpha02","sha1":"a4b748b168eb7c34162c7fa8d9946e8fd7e22b6a"},{"version":"26.3.0-alpha03","sha1":"89310992cb5484c7fb5c8b5cf31679573ee9e133"},{"version":"26.3.0-alpha04","sha1":"7595b6082d1b52c74c01737e5e98a867c8df8cf4"},{"version":"26.3.0-alpha05","sha1":"d9dd17b9643a41fb8703311f2a411db663082c4"},{"version":"26.3.0-alpha06","sha1":"1ff9fa3b34392113c03c8bf06923cf203d4c1cb"},{"version":"26.3.0-alpha07","sha1":"92ed481da12e2f9d7fc2586dc57249f89a18910c"},{"version":"26.3.0-alpha08","sha1":"31a32a59ccdf88ab21a2f39d1a3e2519bdb93746"},{"version":"26.3.0-alpha09","sha1":"b982556fea91ae457ea59608f471279e2d0cf716"},{"version":"26.3.0-alpha10","sha1":"56014da4116666cf0bdb556bbdf4ff53e75ac67a"},{"version":"26.3.0-alpha11","sha1":"31f6eca68bbbeecd084c84f131b57aa394b42636"},{"version":"26.3.0-alpha12","sha1":"84ae3d9fbcf420636a1b1dec2d07afc984c758f1"},{"version":"26.3.0-alpha13","sha1":"2770444692e8004d981ec8cce947fe875b9f3373"},{"version":"26.3.0-beta01","sha1":"c7604abd6904a4e6ba71a74cac475a5381f96fba"},{"version":"26.3.0-beta02","sha1":"4b4d70a64250884b5fab6ec3c830709b98cb2faa"},{"version":"26.3.0-beta03","sha1":"b08d7cac8fe05771cac1e1aac27c26c1ba592149"},{"version":"26.3.0-beta04","sha1":"1c67dc7b7e6091fc2faa3515d3f312c2c2d3d97"},{"version":"26.3.0-rc01","sha1":"3bede2c4af14fe6d914d79d047334cbe051aea53"},{"version":"26.3.0-rc02","sha1":"ce92583d5473b850e62994b331a6582b33654af7"},{"version":"26.3.0-rc03","sha1":"baf05a1e38005eaf722b18c16da56c672a5084fd"},{"version":"26.3.0","sha1":"f42b116e30f5d0f8d17ac2546d47cc70f94f156d"},{"version":"26.3.1","sha1":"971430b6163a834656d07434c46fee64d109ad8e"},{"version":"26.3.2","sha1":"d156ae2bc7d71e955ffaac1c55f3b4d70f2c2d44"},{"version":"26.4.0-alpha01","sha1":"4ed0641cdefda4ef65f988f06559ae896ea7c699"},{"version":"26.4.0-alpha02","sha1":"3fdc4bd51815ff123b23ce4a238c209fbba9f833"},{"version":"26.4.0-alpha03","sha1":"9d103b7ca86d14c6f577996ae7b51f047a6c15b0"},{"version":"26.4.0-alpha04","sha1":"522d361802b52d146cda18379b28413425ab789a"},{"version":"26.4.0-alpha05","sha1":"c2924ab309009a47672b1d80c238d498658af1b2"},{"version":"26.4.0-alpha06","sha1":"5aaec205cd2ee5513c379ff090d4c1a658c40f10"},{"version":"26.4.0-alpha07","sha1":"c041739551b9ff6b5136ad7407a25f66b3b9b07d"},{"version":"26.4.0-alpha08","sha1":"4897ebfb22083d9fece70959099f09e46e645b91"},{"version":"26.4.0-alpha09","sha1":"192a133692d4b3510e6c11a70a5e731c41491c4"},{"version":"26.4.0-alpha10","sha1":"6e400c3f3ae2843fde19f3fcff4eb67ae0469db7"},{"version":"26.4.0-beta01","sha1":"695f533e3e3adc25d7857107ae45fc1d823029d5"},{"version":"26.4.0-beta02","sha1":"54356eef1087d23a34e3a9f3f6ad98040ca1b313"},{"version":"26.4.0-beta03","sha1":"3a571aa7a278d91596acffce2d938ae3483c4f20"},{"version":"26.4.0-beta04","sha1":"ded4240694a25f173642bd1e075eb4c1f36e0adb"},{"version":"26.4.0-beta05","sha1":"80c2bcd347d10a5287e249387e2335fa7ebc9b24"},{"version":"26.4.0-rc01","sha1":"f6f6d66935c56348bf3156f96a5c1d5423815be0"},{"version":"26.4.0-rc02","sha1":"c8dc0b7b04c7924eb9f3acdb99edc139e56681a4"},{"version":"26.4.0-rc03","sha1":"36a571dfec985f62c2a571e38bdb9a49386e6026"},{"version":"26.4.0","sha1":"48ac12dd54ff16487d3553861e108d9e38d5b2f7"},{"version":"26.4.1","sha1":"389d5879b2602e3de5b91ad38588be5dbd57276c"},{"version":"26.4.2","sha1":"c4be78e74b2af8ffdd9eab4285c9ef7718a8bd51"},{"version":"26.5.0-alpha01","sha1":"b56f25d08854c1be3d8f45211775249a0ec4b3c"},{"version":"26.5.0-alpha02","sha1":"58c325e4de97f07a334a506601fa5b5c87a24940"},{"version":"26.5.0-alpha03","sha1":"1f93758615166f1d37677e48bc70c5eb0a118449"},{"version":"26.5.0-alpha04","sha1":"248b2c3f7a4a12b08a6fb57fee4b80150c9e57bf"},{"version":"26.5.0-alpha05","sha1":"cc498d2d74ec556f06df37111edb76f6cfc5128a"},{"version":"26.5.0-alpha06","sha1":"ad7a1ad8943d35b6b8995f1856ea93ce8c5bfab"},{"version":"26.5.0-alpha07","sha1":"849d3ffda1fc94d28221a5324523d47222812d9d"},{"version":"26.5.0-alpha08","sha1":"1cf5bfac6e9fc15eb104917eb6fbeeda4e4cf7d6"},{"version":"26.5.0-alpha09","sha1":"a82f0bef6245c5f2a7a042a7653974aa57b1e8fb"},{"version":"26.5.0-alpha10","sha1":"1cef167a5b19e69ff7ecfdef5bdc3832a3da3b3d"},{"version":"26.5.0-alpha11","sha1":"3a28cf0dc282a118285f198d4971982f0cf8baa5"},{"version":"26.5.0-alpha12","sha1":"d889a3d3a2cffb99932a82db4ee80f26c49258eb"},{"version":"26.5.0-alpha13","sha1":"3b1d3dc1e2ec58663bcfd4d588558aeb7eea2be"},{"version":"26.5.0-beta01","sha1":"84187bec02e80c4b74ad32f0ede8b5c36cc9c359"},{"version":"26.5.0-beta02","sha1":"f3bdc61c69facf01cb3d68cd5e264ad6c07fc5f"},{"version":"26.5.0-beta03","sha1":"7e97f5326842a7e624e7b2600e437174087c4f1"},{"version":"26.5.0-beta04","sha1":"17780184c642724d63df071cc4dfe6d1063531fc"},{"version":"26.5.0-beta05","sha1":"c3d1180f65198752f373b14126181091ab397aba"},{"version":"26.6.0-alpha01","sha1":"ce0a1802672d0b79bfdbf3c3a5a1c693394126ce"},{"version":"26.6.0-alpha02","sha1":"851cc80dea4f3629c24a5e1c7e3754b90301a882"},{"version":"26.6.0-alpha03","sha1":"1556cd7bcce66eceb094a049bd99fd9fa4f8566e"},{"version":"26.6.0-alpha04","sha1":"ff69761084eab899b7120f98831239c397c0f029"}]},{"package":"sdklib","versions":[{"version":"26.0.0-alpha1","sha1":"677c2701ba9bc0cdda29988e27589819710d3599"},{"version":"26.0.0-alpha2","sha1":"420f4a2b4f761853f2dcc4a5dd9d949375ecc3ec"},{"version":"26.0.0-alpha3","sha1":"29f8fdd3efa8696944e322b9bc569b84b75279ce"},{"version":"26.0.0-alpha4","sha1":"e3c170418fb567e2decfda1c5b22a92b7eb17a7a"},{"version":"26.0.0-alpha5","sha1":"8aeb9618bbf00eb5e0c56db6f78ff40330152bf4"},{"version":"26.0.0-alpha6","sha1":"19793a51f0d22825cbe66fbb4b7f8374cc721ef8"},{"version":"26.0.0-alpha7","sha1":"46dfaa0a6b6de4d8d19e162bfb0c05559900a9ce"},{"version":"26.0.0-alpha8","sha1":"cd1ea1f7ff0b02aff2bbfcf7ae0e7c2d311fc9a8"},{"version":"26.0.0-alpha9","sha1":"df1d27d15b2a737930d8fef3bcb516f2fd03b2e8"},{"version":"26.0.0-beta1","sha1":"d3fe2f676173fd159c7531341ae5fcc7ef4afc55"},{"version":"26.0.0-beta2","sha1":"2f0b42a75a050b521bd38c1096637fc9515d1716"},{"version":"26.0.0-beta3","sha1":"a21bbbd450186c1d4d5ea505b3336595162785bb"},{"version":"26.0.0-beta4","sha1":"136fcd1eecdcf5b8aaa77ca3984eefeef19ea9eb"},{"version":"26.0.0-beta5","sha1":"bcfaa28ffbfc854f68610f7ea57743be814500dd"},{"version":"26.0.0-beta6","sha1":"860d072307cf586c6dc811afa51438fb6a1d53c4"},{"version":"26.0.0-beta7","sha1":"b9d8d318df04e7dbcbe964b670c21c006bba3f61"},{"version":"26.0.0-rc1","sha1":"2430e2a63586c378ebaafc4fbc4398995096e0c5"},{"version":"26.0.0-rc2","sha1":"c1fd9fc644fbb97597d4555f2de084abe822dd82"},{"version":"26.0.0","sha1":"5bc6118beed3c516fba2057e5feea3af932c1e22"},{"version":"26.0.1","sha1":"bdb4723f8e3791f1d3911b1f191acf25840f352a"},{"version":"26.1.0-alpha01","sha1":"619974c97623f04140711e835d8927e3446090a2"},{"version":"26.1.0-alpha02","sha1":"cd3b4952bf61a853e7fb7946f3030689521da77d"},{"version":"26.1.0-alpha03","sha1":"924efbd971601c1987a3b32c88f9b0ff6ac56631"},{"version":"26.1.0-alpha04","sha1":"7cf17512a986d74207119a4b119fcd3bf4b95a8"},{"version":"26.1.0-alpha05","sha1":"ce5327d40d7baad471bb4188173784f41beeda52"},{"version":"26.1.0-alpha06","sha1":"645dd6c06a6eed0f6b9f23de713bc8772ecffd9d"},{"version":"26.1.0-alpha07","sha1":"7a65efc07258950e8b7c255b4c4e3b398bf2172a"},{"version":"26.1.0-alpha08","sha1":"a94bc606bff4d765f8f94fb17a970e5d6146633f"},{"version":"26.1.0-alpha09","sha1":"91a7f8975fbc2443f3ada04ca87d28c32b374915"},{"version":"26.1.0-beta1","sha1":"39b907ffde8e926da58a7c7431e933a1d2864a10"},{"version":"26.1.0-beta2","sha1":"d6e8ac0edce9b7d4ec1385ed4db9d1ea7315c928"},{"version":"26.1.0-beta3","sha1":"524ca0328b00450138e0fd052e0e15780f136d40"},{"version":"26.1.0-beta4","sha1":"af4d873de1f11240dafc9303ecea497334e52409"},{"version":"26.1.0-rc01","sha1":"c92f9c8c40d3991306d4595dfd422ebb0010b75d"},{"version":"26.1.0-rc02","sha1":"dc0f0bebe103737f23a8af36ac02962ee027b619"},{"version":"26.1.0-rc03","sha1":"f6adfa0df02f2700ccbeccf5a0aa257f532d02f5"},{"version":"26.1.0","sha1":"3df6e30aed289f4dc7fcd39b7a5aa08d8aedf9e9"},{"version":"26.1.1","sha1":"58465680a6f5d9a07cf38b8c4c7d2bb8d6e2219c"},{"version":"26.1.2","sha1":"94697a9dff499b64b6e101bedb89a89825150af"},{"version":"26.1.3","sha1":"3aa82c583486506621c745b505fa32d43f09ef03"},{"version":"26.1.4","sha1":"7424640f2bd3ca3faccb6f656e29547430cd464a"},{"version":"26.2.0-alpha01","sha1":"c3d83c23f997b2155bcf1183bb7581367781323f"},{"version":"26.2.0-alpha02","sha1":"a69e9341648942960fd896d30e87a8b74b193b64"},{"version":"26.2.0-alpha03","sha1":"9286f7aa507293c1406998b94bc8b094dcb8d51e"},{"version":"26.2.0-alpha04","sha1":"83878979ddfd72626403bff72b01ef2049744a14"},{"version":"26.2.0-alpha05","sha1":"3eb2b7563af3733eb946a7ff50270f0c73b1db89"},{"version":"26.2.0-alpha06","sha1":"b957940310380f455b2adbcd16a281fe3de0b947"},{"version":"26.2.0-alpha07","sha1":"1ec7d6b868c4cc9ddb3e232ea0d4fb40c7261724"},{"version":"26.2.0-alpha08","sha1":"cd16dafd1df6648cbeda9a729a2e7c76c955f81f"},{"version":"26.2.0-alpha09","sha1":"7ef3e0adbacb38895d3e9c9855ef61575398a2d4"},{"version":"26.2.0-alpha10","sha1":"9cf619b945f9e02314748748f2d41460e580852a"},{"version":"26.2.0-alpha11","sha1":"fd2898f6651b323b73f765f9b49c91beb7c6ccdd"},{"version":"26.2.0-alpha12","sha1":"9070bd3f809c8e5d2ae380e88497fdd6b3537454"},{"version":"26.2.0-alpha13","sha1":"cd5040c80c32f3c2de7096996b0943fb5b90f217"},{"version":"26.2.0-alpha14","sha1":"350eb26d436a679d29c65d574fa528852868ecec"},{"version":"26.2.0-alpha15","sha1":"d78b193c34ea7a1a20d8889ff74ef1708e902b87"},{"version":"26.2.0-alpha16","sha1":"a530b1dfc66475c962d9b7cfa85ecbc786060a6b"},{"version":"26.2.0-alpha17","sha1":"3a20f53c6e13cba6d4ad464738380e947d3e41ab"},{"version":"26.2.0-alpha18","sha1":"13ded83cc1ff9bfca88881b936942e7f23d0c81b"},{"version":"26.2.0-beta01","sha1":"d5b85a698652624723ba5ebe92757de50f6342c1"},{"version":"26.2.0-beta02","sha1":"5b18fc2d21e63f7108d80c7d56d79d9a461ec6f7"},{"version":"26.2.0-beta03","sha1":"715e11b59f036486e3b87bb8ca9be6e0c1845cc9"},{"version":"26.2.0-beta04","sha1":"9e8cffd7be77c6266cb5512b926db35ed9bfe195"},{"version":"26.2.0-beta05","sha1":"3ebaa150fd32f736ff956a1a9baa66b73117ae21"},{"version":"26.2.0-rc01","sha1":"bb0d479c56d9dbfccb9942bb066ec5b7535d4410"},{"version":"26.2.0-rc02","sha1":"6a0e8996b9612fd5af64f49344f41ac3e8fcb7b8"},{"version":"26.2.0-rc03","sha1":"17fea07f315bbafc83df37675f40a123ece1425a"},{"version":"26.2.0","sha1":"31067591b057d82ea6c9ba2aee8f98f713a96879"},{"version":"26.2.1","sha1":"6bedd85cbf10816816e71846600ffda8d5037c8b"},{"version":"26.3.0-alpha01","sha1":"90a388962b7e008e20f723d243c2ec38481d9e34"},{"version":"26.3.0-alpha02","sha1":"d9f449ec0c5c416f5c218a70064958f67b85f092"},{"version":"26.3.0-alpha03","sha1":"1b925f554e45cb5299990b4cd947a3a7580c5db4"},{"version":"26.3.0-alpha04","sha1":"b018b6ea8c826b1ace1afd64b1d9983a0e39141f"},{"version":"26.3.0-alpha05","sha1":"b990f0c739d7669098d511bde55eeabb366b6f30"},{"version":"26.3.0-alpha06","sha1":"5bd5e499bb34653bbff5a2cb2264b826fd8bed92"},{"version":"26.3.0-alpha07","sha1":"c539b7541fcfa81aab3dfd2f084e3d2f54bb0bfb"},{"version":"26.3.0-alpha08","sha1":"7bb63d1e001a42d1f0b6bfc481aa4af209a1a37c"},{"version":"26.3.0-alpha09","sha1":"10fcfe3cfdf2f26b5ac9f706f3e93683fbab214"},{"version":"26.3.0-alpha10","sha1":"d9032cdb50cc4af3065c23aa684f3ce1d54a016d"},{"version":"26.3.0-alpha11","sha1":"908bee405c66dd8688057ee0b4e41456f7255c14"},{"version":"26.3.0-alpha12","sha1":"f6f09ed623e05c708152fdb3f107d7340f61b410"},{"version":"26.3.0-alpha13","sha1":"2e73c969ceba5452147ad9f4a65091a058d66562"},{"version":"26.3.0-beta01","sha1":"bbbf2852eb4dba3ce16bd72882b8f42f78c43d4d"},{"version":"26.3.0-beta02","sha1":"301df74d37a65732d021afafcf680570ec6b3cfc"},{"version":"26.3.0-beta03","sha1":"b4a7348ae094624f57062faf94a8b6e757e1bc11"},{"version":"26.3.0-beta04","sha1":"c37ab257fcebe6de642deb7804368d8c25606764"},{"version":"26.3.0-rc01","sha1":"c5a76496ac8a925f951887842d8869812e1c8f5"},{"version":"26.3.0-rc02","sha1":"796a74217f365d331f5b302a546f05eab7fc1a9f"},{"version":"26.3.0-rc03","sha1":"569d161231417a0edd5be25151eabad4ee188f2f"},{"version":"26.3.0","sha1":"e1fdd372c79fbe06a809f5881f936ad1a8b27041"},{"version":"26.3.1","sha1":"85c3c1de8824ca1f5ff491690931256c23a7297b"},{"version":"26.3.2","sha1":"d362e3a45772fcf80eb8105d7c51c524545487c0"},{"version":"26.4.0-alpha01","sha1":"527e6a817f642713825f08be4f037e6b537e84d0"},{"version":"26.4.0-alpha02","sha1":"9a587fe3681aaa49a753894bff7f54472acb2932"},{"version":"26.4.0-alpha03","sha1":"4737d598c13ad33cc4cd92af4dcf9e2177757548"},{"version":"26.4.0-alpha04","sha1":"97f206c63554c3dc548ec97c53a2d978bc0a7fc3"},{"version":"26.4.0-alpha05","sha1":"72a8c21efbc7208f9f4dcb4de2a815f020e5890"},{"version":"26.4.0-alpha06","sha1":"7cd7e7fffd78694fd3bd2ef777213dbd76da48b4"},{"version":"26.4.0-alpha07","sha1":"7fe833c9dbf55eebb353caa32653bdb0d6cf673d"},{"version":"26.4.0-alpha08","sha1":"d1691f7287e566b97109dccec13c16d62bc371ae"},{"version":"26.4.0-alpha09","sha1":"1dc2bdbd6b16dffd8a2996f4f8e73fc2349ff18b"},{"version":"26.4.0-alpha10","sha1":"282ee3bfe546d6a2fb1ca80e173567916db9bf8"},{"version":"26.4.0-beta01","sha1":"6341c9fa7f542025a6ea3f56ebdb1a8a39b02f2c"},{"version":"26.4.0-beta02","sha1":"d407326771a04f8b75c703be7307e4dbb3be3f08"},{"version":"26.4.0-beta03","sha1":"6fcf4f5e3895f020d387720c2a448fbbba809d0c"},{"version":"26.4.0-beta04","sha1":"c19b429f6b286beccb5efd90f45e4fe705bc9fad"},{"version":"26.4.0-beta05","sha1":"81005cfc4bc93d2822e6232a40d0d6bd27e64e8c"},{"version":"26.4.0-rc01","sha1":"86f209ea85cab2e7ae3a3653333d4fce6d06ca5d"},{"version":"26.4.0-rc02","sha1":"929ab63eda0575423bfea60f6c8867e63006ba79"},{"version":"26.4.0-rc03","sha1":"ec1de06113dbe1a1b638b995c225c3967272acc2"},{"version":"26.4.0","sha1":"2249d66f8d8392630a03f59e23f0eef871f795a5"},{"version":"26.4.1","sha1":"5df176d7cd2064c23006f63dce8cee12abff0f7c"},{"version":"26.4.2","sha1":"bb40cd25ee7e1ea620e0c18fdd5492f02d65c613"},{"version":"26.5.0-alpha01","sha1":"78fabbdeb0d87a1ea2c445cd5c8076db5d79c52b"},{"version":"26.5.0-alpha02","sha1":"8f3c07c8865133437b547a032076f7cf0c163de"},{"version":"26.5.0-alpha03","sha1":"2e4437c2557c0180f0ea3a26e3021d07496e9fc3"},{"version":"26.5.0-alpha04","sha1":"fa1368f3fefa757295f21436f0addf69b8e43a8"},{"version":"26.5.0-alpha05","sha1":"ecc2878b7279c2027b0bc7d67db654b0624eaffd"},{"version":"26.5.0-alpha06","sha1":"871d71d8aaf92f4015523ea9059121657a67f46c"},{"version":"26.5.0-alpha07","sha1":"4d27247d3033eb85d34b12255cd10996f63ac848"},{"version":"26.5.0-alpha08","sha1":"49509d2d5fec9317f6d68686a6b519c0b83c016b"},{"version":"26.5.0-alpha09","sha1":"fae1b05b94e0dae693c230727dbd55f8a3980357"},{"version":"26.5.0-alpha10","sha1":"7576aac9fb72a167f33e6cd0f335f42965435fec"},{"version":"26.5.0-alpha11","sha1":"9a3f51d28d81dae555ee8cb5190e3ec707aa4b4e"},{"version":"26.5.0-alpha12","sha1":"33a92cb663fd39e90b133ac338dd2731e7951451"},{"version":"26.5.0-alpha13","sha1":"51ebc603d01c1082d2fb929a5c586d0eb95840de"},{"version":"26.5.0-beta01","sha1":"c12dbe93865f50ac289c70925d05637100b22464"},{"version":"26.5.0-beta02","sha1":"501947b0af9cad5ada1f41adca08a70acfd48960"},{"version":"26.5.0-beta03","sha1":"c928cc9840a2b10747b69e223cb146d78fa3260"},{"version":"26.5.0-beta04","sha1":"b3bdedf33dee684a919d02afa7eeea6055d0b587"},{"version":"26.5.0-beta05","sha1":"37d31cd062b219c869ebb67b1a238edde87b53b4"},{"version":"26.6.0-alpha01","sha1":"146690e1122ccd7a8532df0c7f24d1143c82b35a"},{"version":"26.6.0-alpha02","sha1":"d714c2016ba267e173dbdb9c8f275e3bad44448f"},{"version":"26.6.0-alpha03","sha1":"b4b9f1a8169c5623ef6baeee081f05a34513aace"},{"version":"26.6.0-alpha04","sha1":"a6c4ba5c53c131ae4e1048d615d19c8373e2b195"}]},{"package":"repository","versions":[{"version":"26.0.0-alpha1","sha1":"b29d9ed5354ee6090d4f50bd7cb8475b9188132"},{"version":"26.0.0-alpha2","sha1":"2f588d91471cc310618a732083fe691c12e3b859"},{"version":"26.0.0-alpha3","sha1":"7e97442cf08194486fbefbd77aa31166089505ad"},{"version":"26.0.0-alpha4","sha1":"49cca5db6fc8341901d1fbc21cf82ccd967a0ac6"},{"version":"26.0.0-alpha5","sha1":"7f2126501a5e31954287ce3e6d9c5e5a80581f85"},{"version":"26.0.0-alpha6","sha1":"4b1f9021edd51df888aabfb395621b7bf95dc82d"},{"version":"26.0.0-alpha7","sha1":"4737f3c8d13ab8676706d936204f7e4ad97f8d57"},{"version":"26.0.0-alpha8","sha1":"f0f430d4414956a6a6b833fabb4887db82c7da76"},{"version":"26.0.0-alpha9","sha1":"132b1096372bb47d56ab904175ff832d916aa78"},{"version":"26.0.0-beta1","sha1":"438cef5665781eb2e37aa815597f1bce418a6d79"},{"version":"26.0.0-beta2","sha1":"d5867346eb6d3e9e16be472c090a8ff924a73cb3"},{"version":"26.0.0-beta3","sha1":"3e3c2ef7b7f584ac837e259e55a2c0a4203d3314"},{"version":"26.0.0-beta4","sha1":"ca50eef68323e589cf79cafddff5eba45c9125ad"},{"version":"26.0.0-beta5","sha1":"3c5a420453450413045e755343d906c9ee5f0d2e"},{"version":"26.0.0-beta6","sha1":"1981536fd080a4c5156b8296663e652de9754ad3"},{"version":"26.0.0-beta7","sha1":"1eca2c431cffa61b5467024c78bbde8c84516e97"},{"version":"26.0.0-rc1","sha1":"3db9c025f52e893f7cd8d1d5798d6ab34a71f33d"},{"version":"26.0.0-rc2","sha1":"b1cdcd08e785c144079af11fa4019e3693ea64b2"},{"version":"26.0.0","sha1":"7ad3d113c50642163badf3b8d890c6b41b1c6238"},{"version":"26.0.1","sha1":"bc0dfe02af8d075f6b7ad63a202ed4c63a6a647d"},{"version":"26.1.0-alpha01","sha1":"846c2f44d4ed591ac5de2881c12821c99fc238c5"},{"version":"26.1.0-alpha02","sha1":"33f5f8a24aac78b30d6794d7795e6855c70e8e3b"},{"version":"26.1.0-alpha03","sha1":"d68b298dfcd65a9f1203b8463a37de481761755d"},{"version":"26.1.0-alpha04","sha1":"71936a4db6b902abfb517fdbbf86bc60b36c51f"},{"version":"26.1.0-alpha05","sha1":"fd07161a2ed2a11b3c17865951d9014fc9b8b4f0"},{"version":"26.1.0-alpha06","sha1":"6faa939638cf9413e51951a706ff06bc423786cb"},{"version":"26.1.0-alpha07","sha1":"1b90e3ec760cbf92d46cbd6057933e0c607148c1"},{"version":"26.1.0-alpha08","sha1":"1c163bc6e9af6e2d01e26c6536037af6ab7654b2"},{"version":"26.1.0-alpha09","sha1":"e3b298bf76179d1129395afc2ffa039cb1334772"},{"version":"26.1.0-beta1","sha1":"5298d7c0eb1d278cb2548bbc01cd88a12f56eced"},{"version":"26.1.0-beta2","sha1":"57a2e43d015ec1315facf0fec1ea0eab9071caf9"},{"version":"26.1.0-beta3","sha1":"376b5d6fb7eb48395ba0a79460c1d9bcfd1c7b42"},{"version":"26.1.0-beta4","sha1":"ad79d0df069f38d61505a646b41114d922bf557"},{"version":"26.1.0-rc01","sha1":"4702242cee737daaf4f18f090667771a287c67df"},{"version":"26.1.0-rc02","sha1":"24c9f381dd1b8d8e9bc28d8ef27c597e3f26c2bd"},{"version":"26.1.0-rc03","sha1":"2bf1fe1f2fff358ffe9ad69d1a2c845ee58ba836"},{"version":"26.1.0","sha1":"7b366315ff2180c441ab5ed7948c9786ed0fb329"},{"version":"26.1.1","sha1":"9bfbf1f0b7b2d581bf057c54e0d4de4ee21e102e"},{"version":"26.1.2","sha1":"c8209ccb8ee0e5e3f293fd71d2a827f440ab811c"},{"version":"26.1.3","sha1":"26fdabe9550aa942eaf2c59179c58f55ef0236ee"},{"version":"26.1.4","sha1":"349dfc72ae53dedc32a17f5d47440838fe2527f1"},{"version":"26.2.0-alpha01","sha1":"d228170b7745d6ba19b27d8e16fd70e8fc2c83fe"},{"version":"26.2.0-alpha02","sha1":"8287b2f703a228d0eb37d0255740a1fc7b596185"},{"version":"26.2.0-alpha03","sha1":"37caca501c9cb6590f0ee60b9a76ac717d24913f"},{"version":"26.2.0-alpha04","sha1":"221732f04838c3ca45415d17215d0c7151ef2370"},{"version":"26.2.0-alpha05","sha1":"ee478bc5181b40c896a2764648035a0da666cf8a"},{"version":"26.2.0-alpha06","sha1":"59ac4f76c3d1e72c716edd4527dff70000f01381"},{"version":"26.2.0-alpha07","sha1":"e89078d6d2b7f6ebddcfb79f11c213955f591873"},{"version":"26.2.0-alpha08","sha1":"7aa09390f80aad3f04e6dacf9ed3581de0978484"},{"version":"26.2.0-alpha09","sha1":"b5cb651ddf7e0a6ff200e2df12be2d4197f224c5"},{"version":"26.2.0-alpha10","sha1":"e503faeeb944fba9e799b2172babc2e74b05d7a3"},{"version":"26.2.0-alpha11","sha1":"ca8f28b143cbbc1119d3858e5e652a50c853ddba"},{"version":"26.2.0-alpha12","sha1":"f13d40c6706d06ea91948e027022e84b47508c17"},{"version":"26.2.0-alpha13","sha1":"f55345047c38f0bcd58992368ac988b37700da7b"},{"version":"26.2.0-alpha14","sha1":"52896f00f990ed446e9e1b7e377c83e01a8fc3bd"},{"version":"26.2.0-alpha15","sha1":"9d765a6f290e280c350b1de1d202db31e76eff61"},{"version":"26.2.0-alpha16","sha1":"cacb11e9f1f1d24e4df4b23704e704d8b0c52ee0"},{"version":"26.2.0-alpha17","sha1":"615d4c314c40e523164e91aaf5a12a2d870701e4"},{"version":"26.2.0-alpha18","sha1":"b3ee8a2652a5e9d7b72b1f0e1e11891a50981bec"},{"version":"26.2.0-beta01","sha1":"2cbe068b7f2bd4f560e19691c69a1f0b6f86e58a"},{"version":"26.2.0-beta02","sha1":"1f6ecb32a45b7e03aa2a131610ce00ea7a840ca"},{"version":"26.2.0-beta03","sha1":"8373859edda3d2dafa07778010b0a28e34d0455a"},{"version":"26.2.0-beta04","sha1":"3ebb6e026f4c9783651cf7a3c3170551aeb52260"},{"version":"26.2.0-beta05","sha1":"75046aeef0c35aaca5b207083692bda48bc82fd9"},{"version":"26.2.0-rc01","sha1":"87defb1a90118f592c4dccc4871b99beefdf0077"},{"version":"26.2.0-rc02","sha1":"c9d7e3f4c17614ca6ac39124dbe17c25d591ad7e"},{"version":"26.2.0-rc03","sha1":"8c137618625e633d8cf48e2497803336c6680aa0"},{"version":"26.2.0","sha1":"77bb57fb0339490f96b46a9d608c3002064ba0e"},{"version":"26.2.1","sha1":"7a72673cd6b44aeafab3ccf831fc0b95cb61f3b5"},{"version":"26.3.0-alpha01","sha1":"98412e26b4b3f8312c6593a50a328cec88d597d7"},{"version":"26.3.0-alpha02","sha1":"2e247b7d17521590bcbcb30492dc9ec77ce4c665"},{"version":"26.3.0-alpha03","sha1":"4d0cedb152836f1d8a018dfc58829b5535f08a71"},{"version":"26.3.0-alpha04","sha1":"7225f84f03e77d2f34e21a556d0142c1fbe97489"},{"version":"26.3.0-alpha05","sha1":"de3c8d4ed9d79be65568b7a3f04df26a9b8996e7"},{"version":"26.3.0-alpha06","sha1":"a5226c82157170c33389a2f8d5e0abd3c700650"},{"version":"26.3.0-alpha07","sha1":"8b485b9fd95a4b561600fab852238abb603bc7f9"},{"version":"26.3.0-alpha08","sha1":"34a2c6428607327540f8c96f41c43e9f0c351c13"},{"version":"26.3.0-alpha09","sha1":"dfae2c2829e07ba589d04a57545a6c54d4a790a"},{"version":"26.3.0-alpha10","sha1":"8d2b64e30529eb92b7b0861ed02d264cebe8ccc6"},{"version":"26.3.0-alpha11","sha1":"69bbe297db493c3c7f438e0d80c481410c755fee"},{"version":"26.3.0-alpha12","sha1":"98c6f46e29a2e4a13d33c08e8dbe5e353b5d5e5b"},{"version":"26.3.0-alpha13","sha1":"25d6c55005646eaae3da578e9a2318f79cab23f6"},{"version":"26.3.0-beta01","sha1":"84f8b992dd5229216df43205fc6216cac649680c"},{"version":"26.3.0-beta02","sha1":"160325b92f86c86acd5ba04af89df40b04e45797"},{"version":"26.3.0-beta03","sha1":"b75adce598c29f3588e0f378ef69f0a513a66876"},{"version":"26.3.0-beta04","sha1":"543b4be8e4e746ea7db514351d0f6d21d5c78682"},{"version":"26.3.0-rc01","sha1":"739a8809bac41ac0b6280804eb09cb4fae718b2b"},{"version":"26.3.0-rc02","sha1":"9475d1462377c2f0db1a26e1e10e28159ab5d13a"},{"version":"26.3.0-rc03","sha1":"39ac8b67e257f23c30bd27207905f1792a809191"},{"version":"26.3.0","sha1":"b6098324bb21b89d4ef02e68fda840c6cc3ddd06"},{"version":"26.3.1","sha1":"fe4c713437ebfb8c68e95c717c063843bd63758"},{"version":"26.3.2","sha1":"c8fd4e9c1a6b3fd041503a0d52fc233301e6e7b7"},{"version":"26.4.0-alpha01","sha1":"69c55838fec26f305a031fd89b330d20ff3428ba"},{"version":"26.4.0-alpha02","sha1":"ffd8c1855bb174c4916096f041238a6f2a44cbe9"},{"version":"26.4.0-alpha03","sha1":"b7a9a7c3cc760688d42f6fa5a5eb858bb9f90acd"},{"version":"26.4.0-alpha04","sha1":"5b95b208537e0bde0ea7e31272b0f12a2e9ee6b5"},{"version":"26.4.0-alpha05","sha1":"aa336f6113ccc51e4a8438c585a525bd66ee7a7"},{"version":"26.4.0-alpha06","sha1":"50beb9e7333a2f247cb170346862845005b46c2d"},{"version":"26.4.0-alpha07","sha1":"7190ab5e694598340f8fdce30d0a9bb257880bcf"},{"version":"26.4.0-alpha08","sha1":"c414985a8b13e27f258349d1008a6eb33e508e32"},{"version":"26.4.0-alpha09","sha1":"58caf6c56ba2dd5d3ff7508de888f16e78cd0fdd"},{"version":"26.4.0-alpha10","sha1":"d666b0361f62fabc64ce8370ecfad46705b235c8"},{"version":"26.4.0-beta01","sha1":"5d592bf65b62c99f6ad941b2bd0fe58db82616d3"},{"version":"26.4.0-beta02","sha1":"1529a10b68d576d23f0c21bf4d314cf5e0e4e20e"},{"version":"26.4.0-beta03","sha1":"2264de887ce1a5dba51380919e283371e853bd90"},{"version":"26.4.0-beta04","sha1":"423d68c57143f5f60a1f8ede4464e775cf3be5b0"},{"version":"26.4.0-beta05","sha1":"4cb53cc0c61422055983aa8f37a62b34349f5aab"},{"version":"26.4.0-rc01","sha1":"71e1f34767eac85b43aa5a1fa315e820b96fcc0e"},{"version":"26.4.0-rc02","sha1":"7c77ffbd8581b0500c955fbde94794dbae3e7cf6"},{"version":"26.4.0-rc03","sha1":"fa9697659c1f1129328ccbf11b4a22d218a4eadb"},{"version":"26.4.0","sha1":"d91be89a520ae1e1d0abcc41e87afa5f905a8dad"},{"version":"26.4.1","sha1":"614275977f26ff273fcab4b2e67f872868a9af95"},{"version":"26.4.2","sha1":"e97cf07400adc5128f5a351030f4930f83120d4b"},{"version":"26.5.0-alpha01","sha1":"107da5e302b320715a3def6df7a8d682d037cfc8"},{"version":"26.5.0-alpha02","sha1":"6aa6eaf54416825e8ab7cb799f534929d2fd3c9a"},{"version":"26.5.0-alpha03","sha1":"8ad522ca29e027e5cd2722e38ec3a1003f87533e"},{"version":"26.5.0-alpha04","sha1":"e00c87d8f74b6b9e85573d6b84119dd60abb215c"},{"version":"26.5.0-alpha05","sha1":"30e0b8d96e882843c737c43442b36ccf77e1b3c5"},{"version":"26.5.0-alpha06","sha1":"729ca17dfafbd71fd353e6aacc89bc5a5fe27b9f"},{"version":"26.5.0-alpha07","sha1":"69288b455e7639c1ed1d88b85da7bc797c293fb3"},{"version":"26.5.0-alpha08","sha1":"720dbef3183020ef7eddbc8c3333dd7a45da997"},{"version":"26.5.0-alpha09","sha1":"f8781e2f5fa7dd3e11a3a719034887914f7c351c"},{"version":"26.5.0-alpha10","sha1":"c72bd842e1702cde710525672b7827b643116e84"},{"version":"26.5.0-alpha11","sha1":"db28c5ded3c7a41c44e112f07cd3308738e9a461"},{"version":"26.5.0-alpha12","sha1":"5be1a229a8c11b77c5d46512682f1883fa9f244a"},{"version":"26.5.0-alpha13","sha1":"93683d4d5d0a0dd01c79e7606b1701b988f2d172"},{"version":"26.5.0-beta01","sha1":"57e9692a2f5ddab25140d685623649f6bb24907a"},{"version":"26.5.0-beta02","sha1":"41f2de02227b2bdecf0c2891ef9543479538b0ef"},{"version":"26.5.0-beta03","sha1":"cc70dfa275de4e51e5c63f3e9329afa1efd51ea6"},{"version":"26.5.0-beta04","sha1":"8ee2bf65a43bd7ff54ee33d64255f428907727ea"},{"version":"26.5.0-beta05","sha1":"701e1c6201861229b0b75f71e512f3e2a6121fd5"},{"version":"26.6.0-alpha01","sha1":"c68aefd79cd8a426480be6e315a2b3bb35aa307e"},{"version":"26.6.0-alpha02","sha1":"151dc4792e356adbd522702411a6c37d15d98ca5"},{"version":"26.6.0-alpha03","sha1":"883e211134f0a0f7ead0216475e701acef1477ba"},{"version":"26.6.0-alpha04","sha1":"8aecdfac5809b11604105a7047c850460f0bc89e"}]},{"package":"annotations","versions":[{"version":"26.0.0-alpha1","sha1":"f9522c9c4644cd7f6ca780463839f55d4e0a7c79"},{"version":"26.0.0-alpha2","sha1":"a6944b3c665d188e815137ea5921e2a02813b9fe"},{"version":"26.0.0-alpha3","sha1":"1193ee1680017b6b1d78e7568afcf1ba2fefeb71"},{"version":"26.0.0-alpha4","sha1":"7a46c6985c763587e295fa96f09fc7ba4c857a68"},{"version":"26.0.0-alpha5","sha1":"c0b239f898e3bab7c42258ff5e889288e583658f"},{"version":"26.0.0-alpha6","sha1":"b1c7ee15e16fb068d8ec24c1366df1871ee78f69"},{"version":"26.0.0-alpha7","sha1":"d2aea68c3203a8ed77e5a1f94f77cc3ac1f92646"},{"version":"26.0.0-alpha8","sha1":"d419b4fdba90c2b590abda01b25f5b12863782ec"},{"version":"26.0.0-alpha9","sha1":"f2f0174f5fb153d1ba951c7003c9aa917ac87e41"},{"version":"26.0.0-beta1","sha1":"e87a1e29b69cf35356056427175be51ae6ed32b1"},{"version":"26.0.0-beta2","sha1":"5e50c6e8bdef55c7e9dae1a88d1b3f88bf8fe292"},{"version":"26.0.0-beta3","sha1":"9db4a40faa1c9afba698b5454b495a30abd4b726"},{"version":"26.0.0-beta4","sha1":"8cd2ce5937ff94cdc86f6cf62e167f5795eb1eb"},{"version":"26.0.0-beta5","sha1":"31ce1abb3edbbf4a3b02b409892a9e2137f8dec8"},{"version":"26.0.0-beta6","sha1":"e7907928057434fe8958be5316bdb116daf26189"},{"version":"26.0.0-beta7","sha1":"e33a5fb490405de55e1fbed0cac535f80411a260"},{"version":"26.0.0-rc1","sha1":"41bc48e1477e1964d24a9ef3e5844ec495a957d0"},{"version":"26.0.0-rc2","sha1":"111f744c7f7b0037bbcb09a0eb44a985505cd5d2"},{"version":"26.0.0","sha1":"fc587b91f1d2390a90d0b02a3e41a0bed140e9c1"},{"version":"26.0.1","sha1":"29012758503f56c7dc0714eda494f7f140fd3db8"},{"version":"26.1.0-alpha01","sha1":"f7708f99b82141ba6d7d7547b7d19af93ab513a2"},{"version":"26.1.0-alpha02","sha1":"1bfee25576627db17d4c7e68f673fbeb4d6a20df"},{"version":"26.1.0-alpha03","sha1":"72c339c4219f202ff05e10fb6deff530064ea89c"},{"version":"26.1.0-alpha04","sha1":"5b0659a92e4c47764823c4816023a17f770cd088"},{"version":"26.1.0-alpha05","sha1":"8c4b68567ba522cd0b404f8e73f82da52a7b9957"},{"version":"26.1.0-alpha06","sha1":"101bdd87e2d85802d4503a6693d4f588a26c5807"},{"version":"26.1.0-alpha07","sha1":"cbe23f1e08ef6c8c4087a07871fb9229af983ee7"},{"version":"26.1.0-alpha08","sha1":"b2fea4499b0ec97e7550d1ab278b01f873b9f4b2"},{"version":"26.1.0-alpha09","sha1":"3df3c5a3cb5d4464f16d2fa4f0a271e7f6d637d1"},{"version":"26.1.0-beta1","sha1":"b70bdbc851337571f1313e6b62235ea726e4023b"},{"version":"26.1.0-beta2","sha1":"995f004496dfa915abf65bdf4dbe167b549cf1ed"},{"version":"26.1.0-beta3","sha1":"e5f4f6fd81d1eabd299967b85b5ab79108539171"},{"version":"26.1.0-beta4","sha1":"b34a5ac7f00fb6f0dc7d9092dea6fab1c9a60394"},{"version":"26.1.0-rc01","sha1":"afba40529ad7aca84976352d8b819cc9bd98acee"},{"version":"26.1.0-rc02","sha1":"8fa0529695889cbc77487139930e45ab502472b5"},{"version":"26.1.0-rc03","sha1":"9882d2b0d97fa120d6562adb3af43c5df6301f28"},{"version":"26.1.0","sha1":"dfede8a6180c47422a92717747863fa1263e2834"},{"version":"26.1.1","sha1":"e91b00c2fa7d3139a485d53e0e382432da677a8b"},{"version":"26.1.2","sha1":"4f4e0ee71b9ccaa4a70cc86e40fb84ada2ed99a3"},{"version":"26.1.3","sha1":"b3372d7a713131ce5022b82db95407c2584a6b27"},{"version":"26.1.4","sha1":"817d5238d9ea07b4b26d04e29e9bada2aacb7245"},{"version":"26.2.0-alpha01","sha1":"9f38e0937dd81a898bc3446f5a91dcdd37099ece"},{"version":"26.2.0-alpha02","sha1":"8fe327ad261d0725bbb39a8de9cac4c3ee1f3134"},{"version":"26.2.0-alpha03","sha1":"5e5a8a3eb62590399d0a0c6e1ff42577737e58e5"},{"version":"26.2.0-alpha04","sha1":"b20751e9cf4722ef6df795604962d69ab4e515a4"},{"version":"26.2.0-alpha05","sha1":"d218b5ab854adedbc632ee05c2a58d6cd1fa798f"},{"version":"26.2.0-alpha06","sha1":"c3f122b2cbe8fb9c546cc25fbe45a6d86067b732"},{"version":"26.2.0-alpha07","sha1":"c54f958098eb20a6f8c002c3bc1794a01ff0c531"},{"version":"26.2.0-alpha08","sha1":"e443fa0d13ff8701aa965eb0a86318f159821e94"},{"version":"26.2.0-alpha09","sha1":"f16b3f88d6f9edf6233ff41d8eea578864efb93f"},{"version":"26.2.0-alpha10","sha1":"21fb9e173a7d0b97213135642a8f7d255f834cd7"},{"version":"26.2.0-alpha11","sha1":"4fb66952f9709b37f55f4aecad7baccd69924129"},{"version":"26.2.0-alpha12","sha1":"13bd85ec704af585e582513e08621859cd3cc2b1"},{"version":"26.2.0-alpha13","sha1":"3d90835543eafa28ea700c566a1761ec0c8cbd8d"},{"version":"26.2.0-alpha14","sha1":"aebd711ac0a88ec8fd046b41019294bf6715fc68"},{"version":"26.2.0-alpha15","sha1":"62ffaf22e93d586a1b0d9f3fd538f2f16b556c37"},{"version":"26.2.0-alpha16","sha1":"1f369e037c84ac17272dcce700ef6eaf0b187e28"},{"version":"26.2.0-alpha17","sha1":"fdc6bb21e042851927624a8fae463d5c95f35ed2"},{"version":"26.2.0-alpha18","sha1":"25d4f8166dff97c05fb09bde88ce45f36c8a6862"},{"version":"26.2.0-beta01","sha1":"24f9180107d8eafe794b7ebf0c8a407588b04313"},{"version":"26.2.0-beta02","sha1":"e5f23f73f048a5f4ad7bbec61d0cf2e09474e463"},{"version":"26.2.0-beta03","sha1":"1e4ecc63aac0f1415de2659ce983d540fa37723e"},{"version":"26.2.0-beta04","sha1":"8b9f298f2f88deda9e73bdb7e96df7af9c87b58e"},{"version":"26.2.0-beta05","sha1":"1384e19dac29e3c28219d6819b33b8acdd686baf"},{"version":"26.2.0-rc01","sha1":"284307d2420b2eda45604fc0c6305f5eaf624284"},{"version":"26.2.0-rc02","sha1":"99285aa483dc2ebbb5799e08bac0d210f8bc128c"},{"version":"26.2.0-rc03","sha1":"4d26272c11340ecc222f84e04aab91220edb449b"},{"version":"26.2.0","sha1":"e1c021729dcc35bfc5784a1def99021254f2d262"},{"version":"26.2.1","sha1":"5f9b112634e53890e16cc2947989bc2bc048734e"},{"version":"26.3.0-alpha01","sha1":"87cc56930c98303fa458bed9cca027f19b6e3736"},{"version":"26.3.0-alpha02","sha1":"31ffd8e68d7d970252ee30c0a5a08845d9a8c0ab"},{"version":"26.3.0-alpha03","sha1":"306e473760d13eb00653897309004a6ffd5b0630"},{"version":"26.3.0-alpha04","sha1":"d9db6354f4e9905b009e4b5180b007fb4fb74693"},{"version":"26.3.0-alpha05","sha1":"ef8b6066a58e6f1216272c3ced4af23078cf74b8"},{"version":"26.3.0-alpha06","sha1":"9937e8928e4123031712f8e9c3770be398dfbee1"},{"version":"26.3.0-alpha07","sha1":"4733076e3f602657ae2928a93b9674dcf5f08876"},{"version":"26.3.0-alpha08","sha1":"83338097a64b8da05770a7ec199e4f3224a44945"},{"version":"26.3.0-alpha09","sha1":"bb48f923044e93fbff5341fdff36b6fbd103b01c"},{"version":"26.3.0-alpha10","sha1":"a025b6c06ced78b55550b3b9328089ec57cf27dd"},{"version":"26.3.0-alpha11","sha1":"d13e55fcbb1033f6cba178afae15a0248c2bac8a"},{"version":"26.3.0-alpha12","sha1":"15ba574acba169fd19def3b7d87545ae41cf62fe"},{"version":"26.3.0-alpha13","sha1":"a8ae31b09533bfc2b5630f88045a7dd47ef406ee"},{"version":"26.3.0-beta01","sha1":"8fbf24187b0088f7c8fa8bbe76e7ac90c835ac1e"},{"version":"26.3.0-beta02","sha1":"6aa0cf243af19a5a320ecfe5fa822ab1fefa4b1f"},{"version":"26.3.0-beta03","sha1":"a34aed4f79ad8f30581f853896ed0f21a25290ae"},{"version":"26.3.0-beta04","sha1":"400ac0c898e423d3dfac7bf18afc1096edca3204"},{"version":"26.3.0-rc01","sha1":"673e8bff18707ab0b6590c6361393285efe089b"},{"version":"26.3.0-rc02","sha1":"bf803d678ae541c93c11ee5ade201ebdd920c4e0"},{"version":"26.3.0-rc03","sha1":"afb9eaba30dc8ddcfb631fe29bd736177bb5bddb"},{"version":"26.3.0","sha1":"850899c26903b514387f9f437398bc4095a91877"},{"version":"26.3.1","sha1":"3d46d5da6d5dd17df682a22b914cdc6b84677731"},{"version":"26.3.2","sha1":"b44255ac410552d5e65e159830596b2a9a0a3d9a"},{"version":"26.4.0-alpha01","sha1":"2008c7893979fb9d1058f6253dcda502bcf4d655"},{"version":"26.4.0-alpha02","sha1":"1c71db557e60404243385a73f071ecb7cf82ff28"},{"version":"26.4.0-alpha03","sha1":"e5edc1a5867ac32286600bb640b247644e4c5afa"},{"version":"26.4.0-alpha04","sha1":"9b4440c775f6af92afa6773e821b8417aebae8ea"},{"version":"26.4.0-alpha05","sha1":"92ee58f5298af217dbeb8db6b50852c6e4635883"},{"version":"26.4.0-alpha06","sha1":"43527aea6f8fe19a2e889dd98c008ab34e89dd05"},{"version":"26.4.0-alpha07","sha1":"8f431531d0fa4f28e7821971c97de17629a09de4"},{"version":"26.4.0-alpha08","sha1":"bda61019e3d5a7bb2f23f4ce99c45d8ab405c796"},{"version":"26.4.0-alpha09","sha1":"c54daf4b84db3480334ae6bb6cafa041483a56da"},{"version":"26.4.0-alpha10","sha1":"836af215c54c0fd085aadf0955added43044f3d6"},{"version":"26.4.0-beta01","sha1":"69708b37d67ff1f958ff65b0c4532c8791b8edc1"},{"version":"26.4.0-beta02","sha1":"8d566bde3c5e8b48ce1ac5035ccabd7a73b360"},{"version":"26.4.0-beta03","sha1":"7e7195094f2c524875d4d5a31fbfdbd83fb6364f"},{"version":"26.4.0-beta04","sha1":"7dbaabe867768d86f84b46ed3de46230cf1a0ff2"},{"version":"26.4.0-beta05","sha1":"252dbf98e84efd107fab619eaa94ee7290e77f75"},{"version":"26.4.0-rc01","sha1":"eb7272bd38f5f3fadea15b59fce6fc0ce49c1f6e"},{"version":"26.4.0-rc02","sha1":"dbc674deb6cfb303932814de8dd6dcc0fe24791e"},{"version":"26.4.0-rc03","sha1":"224583542bec9206de3be55a0b676a1112ae608f"},{"version":"26.4.0","sha1":"99ccefd34e8488ece2098165d0ae1eedb6491797"},{"version":"26.4.1","sha1":"68e90a2f48445f4f2003640c539e0cbe02fdcb5a"},{"version":"26.4.2","sha1":"478e20c965512ba805a25620d8e98d57fa2b6b68"},{"version":"26.5.0-alpha01","sha1":"21dea0f0f221de79e71506e18d730e0200e95dd2"},{"version":"26.5.0-alpha02","sha1":"ec3496539900542c74c89f0b4e21c4e951b8a4a7"},{"version":"26.5.0-alpha03","sha1":"ce35839ddab404846e56cf6ebf6e695d9a42b688"},{"version":"26.5.0-alpha04","sha1":"56596b310bd66f4d5213b0ee796472c30b8a0c80"},{"version":"26.5.0-alpha05","sha1":"a0b5f16e841bdb092bf5914f333d7fc9ebba6477"},{"version":"26.5.0-alpha06","sha1":"45c57d88668075bd01344a492844105d5b687805"},{"version":"26.5.0-alpha07","sha1":"42b5039492c7ce053b1d073f8dcd5f001e8e8ee7"},{"version":"26.5.0-alpha08","sha1":"98ff33a50454bac1bc61f93f380ce771062e55d2"},{"version":"26.5.0-alpha09","sha1":"80aa68b22b8bb7570895cd5f81a97e0c6ca9bf6e"},{"version":"26.5.0-alpha10","sha1":"e7f77bdb858370f10efd4a3dff7890696d9fe751"},{"version":"26.5.0-alpha11","sha1":"7b8ce016ad90c56eb69bb1d4d679f57ac7e70aaa"},{"version":"26.5.0-alpha12","sha1":"6126d9945206855ffc49014cf298fef1fa4bc626"},{"version":"26.5.0-alpha13","sha1":"2ebdaf687c5855b5927fd0cfe598f12cb7301594"},{"version":"26.5.0-beta01","sha1":"c0b42f3a9d8926cf107d801092c0d8e76941fa81"},{"version":"26.5.0-beta02","sha1":"842789892b03254a0404dd6dd5da3b371974a8e9"},{"version":"26.5.0-beta03","sha1":"52b1cad6eb81dc59d42eebfc440b827be5618081"},{"version":"26.5.0-beta04","sha1":"2e8a036c143e1713b7c45ca65de511d88d14d17f"},{"version":"26.5.0-beta05","sha1":"d37418d147505846f10fd9af31249816112c1ce3"},{"version":"26.6.0-alpha01","sha1":"f9a48a312592abbaa966663c40fd84d92ae394ff"},{"version":"26.6.0-alpha02","sha1":"a74f43c22292d39132b757a6a89733559990d12a"},{"version":"26.6.0-alpha03","sha1":"cd3e86875d0abbf1c5310396860add65fad9c162"},{"version":"26.6.0-alpha04","sha1":"536e1c5b0cb0c6035fd26ab138ad32023a789f1a"}]},{"package":"devicelib","versions":[{"version":"26.0.0-alpha1","sha1":"f8df8f9c8151227bb9c81286885c992fbc88cc0"},{"version":"26.0.0-alpha2","sha1":"77a4fdbd6df5ed6d4334564f6db3d4d47aa4b6f3"},{"version":"26.0.0-alpha3","sha1":"ab26f69ff6890fc5af9808ac4302042ed6d9c6"},{"version":"26.0.0-alpha4","sha1":"e3fceb0c4c63f6c836bb0a67bf27372d53d554c6"},{"version":"26.0.0-alpha5","sha1":"97be3980b6b35e8846e2923071d0d530f1f7c050"},{"version":"26.0.0-alpha6","sha1":"42c920851e60fb421a2edbef08d248af86f56794"},{"version":"26.0.0-alpha7","sha1":"1a42f7b49b6ebcf8aacd6ddff1ce6b77323b792f"},{"version":"26.0.0-alpha8","sha1":"6a29d9d9ed0ef4eb7a0ba4ba30c5c3107c0804c"},{"version":"26.0.0-alpha9","sha1":"7529a630c72e668f3c8d842a5c28dd279a620bfa"},{"version":"26.0.0-beta1","sha1":"f0891dc7c55105f9d51cb5965257087a99c27252"},{"version":"26.0.0-beta2","sha1":"cfe326f2a6faf7e7b7d31c2e55185e81a7b008da"},{"version":"26.0.0-beta3","sha1":"db4bdcb031ef1cd5a475e0b14af3222cf1ecbc37"},{"version":"26.0.0-beta4","sha1":"fd233fa577a01fde172251ebc496a3549d18cb89"},{"version":"26.0.0-beta5","sha1":"825e825055af14b40d127990c2b7f35f2c6467b9"},{"version":"26.0.0-beta6","sha1":"ec671c54c4dd41c24d484b0e739d677fcf4635ef"},{"version":"26.0.0-beta7","sha1":"3b0d4b0c848bbfa396147c74ad62d4a55d926d0d"},{"version":"26.0.0-rc1","sha1":"5f0ef4cd6be05689ac385184f97fadad4619c7e9"},{"version":"26.0.0-rc2","sha1":"8b491eceeb237a45610b11f20628574fb2450f96"},{"version":"26.0.0","sha1":"87ad8f6ae460d57578eabda23c9566d66c2deb37"},{"version":"26.0.1","sha1":"9ea2fb85c146d0eccac2a752d401c3738c35645d"},{"version":"26.1.0-alpha01","sha1":"ead61cb70081b56950beedaf4967ec25f7ad0a1c"},{"version":"26.1.0-alpha02","sha1":"cf22ac0997d989318eb28b1b8d0a7d138116499e"},{"version":"26.1.0-alpha03","sha1":"3cf6e0786d3ce4aafa6b744e3f50edde7b139731"},{"version":"26.1.0-alpha04","sha1":"a05e66dae4c645e4b563360465413e3afd5b2299"},{"version":"26.1.0-alpha05","sha1":"3d18258c5f4ea8972babfb4ba2f0f76d6b8d58fa"},{"version":"26.1.0-alpha06","sha1":"54783342c42ff4c78c02ffa37fd10c59d45f6375"},{"version":"26.1.0-alpha07","sha1":"f412c6d1b0208df48d15e42a9111383701ec5d9b"},{"version":"26.1.0-alpha08","sha1":"28ab37a98519c80cdb13e4689688e49f2303c3ba"},{"version":"26.1.0-alpha09","sha1":"56a59d23e53082ddd7cef3c123fad1c9606354ee"},{"version":"26.1.0-beta1","sha1":"67ef37ee6d3af1bd8bfdd100f9a3b4cd13281d7"},{"version":"26.1.0-beta2","sha1":"84f2ceae4d24cd86f944a294c0cfefd2f2c2eaed"},{"version":"26.1.0-beta3","sha1":"3a9f2dcd9d86add417e248284daeff5549398ce"},{"version":"26.1.0-beta4","sha1":"d9ab067b5e9195e926233a3597e7411ce3c185b9"},{"version":"26.1.0-rc01","sha1":"b30709d3951afedb09d53e19c04b3a2d165b0edd"},{"version":"26.1.0-rc02","sha1":"276ce3112aa4370aa5d5be20014444384dbe6206"},{"version":"26.1.0-rc03","sha1":"8fb29725ef23bda9f515795a860333dc27bca953"},{"version":"26.1.0","sha1":"649fab734c8ab7a372d5424e6b40753e299bca94"},{"version":"26.1.1","sha1":"10acab3d0c77ec3a799cf5cdaf6f45adf31c058e"},{"version":"26.1.2","sha1":"c7d447ae2f3c7cebb4e564f6061b95d0be409a61"},{"version":"26.1.3","sha1":"8a9ea8853b054233f87f20d8ac782cda0bd76ea1"},{"version":"26.1.4","sha1":"d4652c2975e19c8a1cae93dde515c934877b1e6b"},{"version":"26.2.0-alpha01","sha1":"3f525619406b27332f024df1988e621f18488db8"},{"version":"26.2.0-alpha02","sha1":"4efc50ae8e70780c54d9b62ae48489e0cc2153a5"},{"version":"26.2.0-alpha03","sha1":"d732a0c94e17b5ca87d5da78b820a80933eb4a95"},{"version":"26.2.0-alpha04","sha1":"10c467a20a7da5e6fdc1de9bd1c929fe6f538d64"},{"version":"26.2.0-alpha05","sha1":"bd507a3c0131d1b36b3d8c8fd77d85ea3b37a0f6"},{"version":"26.2.0-alpha06","sha1":"9344beac7fece6034a1ce698df4bd83abb07050e"},{"version":"26.2.0-alpha07","sha1":"a3422c36ed3de577c48c476124e600bd58cc001a"},{"version":"26.2.0-alpha08","sha1":"20fa0f82fee35b7e8ea52bc7d72e13556e45ca3c"},{"version":"26.2.0-alpha09","sha1":"da14b2d44384bd0a353973843dbf46ea1a50722"},{"version":"26.2.0-alpha10","sha1":"a2893ff4f447f0a24d246d3933f15304ec8d356d"},{"version":"26.2.0-alpha11","sha1":"8877d6a5412d76822598a66144bde7664ce61429"},{"version":"26.2.0-alpha12","sha1":"2d838fbfba02f6f24815a13eec26af5a5b735c85"},{"version":"26.2.0-alpha13","sha1":"fc23254d8b5fa9078d97d42bd04b6f9a51f848ca"},{"version":"26.2.0-alpha14","sha1":"dc320d3ceaaca9dc51e9f4e76ed5a2504e6f499"},{"version":"26.2.0-alpha15","sha1":"2ffee4bce33c6aa00e89aa37b2bd1a0bcc783974"},{"version":"26.2.0-alpha16","sha1":"e683343f7ee2d0309a1617a22c06881ceb4d36f2"},{"version":"26.2.0-alpha17","sha1":"a1aeafbf71a1d4bad90587c3edbdd1b50b881294"},{"version":"26.2.0-alpha18","sha1":"b72626097e5ecac6fe9334d1f16942ca8f8c498e"},{"version":"26.2.0-beta01","sha1":"c4bd33c6c008c6100bfaa58ab3ee30cadce19172"},{"version":"26.2.0-beta02","sha1":"902142d1168e1ebe840166e544fdfb41409cf8b9"},{"version":"26.2.0-beta03","sha1":"1464cefc0f0340c50e809de6cf6a14aa990024ba"},{"version":"26.2.0-beta04","sha1":"570800856b34d342ed77252c3bc15d37246662fb"},{"version":"26.2.0-beta05","sha1":"9a28d48118334dac19baec523799b3a680f70ba6"},{"version":"26.2.0-rc01","sha1":"f65dccd11e7f9ae1439d16927065deed7fc81d8e"},{"version":"26.2.0-rc02","sha1":"acfbe1faeb839accb5043d16a3e44de0ebdbc4f3"},{"version":"26.2.0-rc03","sha1":"d8e9c4f25cf48f73f34b15d743565a4a34b1b58d"},{"version":"26.2.0","sha1":"87a1584bdeaa8b80f416eb95006de5234a71c44a"},{"version":"26.2.1","sha1":"be1a46f708e38c256bb8b8c753ee3a80b01db785"},{"version":"26.3.0-alpha01","sha1":"47fead7f1d139684b430c02cb739681c64451813"},{"version":"26.3.0-alpha02","sha1":"ab7e484153d19a0a8e855428594e6555b64dadf6"},{"version":"26.3.0-alpha03","sha1":"7d57873856541d15dcf37befa923223b38161c08"},{"version":"26.3.0-alpha04","sha1":"31aa15b9cb08b55ce9b6a82c99304d20bd31cffe"},{"version":"26.3.0-alpha05","sha1":"684fe7b87c9af72de869caf6b76009012064418f"},{"version":"26.3.0-alpha06","sha1":"9cfbd3dec0af2c71e53029df8a1b97166b1b8c81"},{"version":"26.3.0-alpha07","sha1":"a8dc05ab1b343876b6fccd25a3d3e20bd671cf9"},{"version":"26.3.0-alpha08","sha1":"b66de7fbbe926e6edb86c1eb3b77aaca4fee8627"},{"version":"26.3.0-alpha09","sha1":"f27dc1b6625624444c1dc3703b412696b7670993"},{"version":"26.3.0-alpha10","sha1":"9acac385a602aad88fcfc45492312cb46dfe81f2"},{"version":"26.3.0-alpha11","sha1":"ba404720e3706d9bd6d60107f8ad86cbe5d4bc90"},{"version":"26.3.0-alpha12","sha1":"24be39191fafe1e3f20ce53e3edfcee98e79afec"},{"version":"26.3.0-alpha13","sha1":"ad91958473d5193f4a27af45d33f2819f4de99e"},{"version":"26.3.0-beta01","sha1":"43bca468bef194b66bc7aff1876a69db45b798ad"},{"version":"26.3.0-beta02","sha1":"f2ffbef93ead04596269861e789717b2c9b0076a"},{"version":"26.3.0-beta03","sha1":"ebf3e3b385b6d64d8dfb8cdaa65bdbc6141f8389"},{"version":"26.3.0-beta04","sha1":"4d4334cdbb799e96cac0f2190110af9a5b3eec57"},{"version":"26.3.0-rc01","sha1":"26f15fc98b347e233b791e5a8938a50ae8e3b0bd"},{"version":"26.3.0-rc02","sha1":"a2e46a52e404fe28557e5261bd1ff64ddf63eed2"},{"version":"26.3.0-rc03","sha1":"5f8f77f2a14fb62ea39fae64190a1a393ef4c6f1"},{"version":"26.3.0","sha1":"5d415dc42c4ab5aa5d870552740f70ec4afc9e00"},{"version":"26.3.1","sha1":"5583650c86692933e19f7056465a77c61669d70b"},{"version":"26.3.2","sha1":"abd0384cd747fb0b03e6210eeeb5d57904046caf"},{"version":"26.4.0-alpha01","sha1":"d227d25bc48cdd6f6c8a9abe77802e41fd5e0261"},{"version":"26.4.0-alpha02","sha1":"ebdd759aa10048f3dd1fc9943a97188a90ad6ef4"},{"version":"26.4.0-alpha03","sha1":"5126ab3bc2f252443213866e0a540df76ec9deab"},{"version":"26.4.0-alpha04","sha1":"19b5cb7795ade52ed76050762fd5353cbfaf6249"},{"version":"26.4.0-alpha05","sha1":"709926bf998c365f777845d672bdbdd9509b85c"},{"version":"26.4.0-alpha06","sha1":"c0e454c4cdaaddd4d8473523d135542c91d3e3bb"},{"version":"26.4.0-alpha07","sha1":"f32d1bd0b1aeac6b279a88a392a28524c0ef1667"},{"version":"26.4.0-alpha08","sha1":"c71bd1751dda71de0a0912de021b2096806e34c5"},{"version":"26.4.0-alpha09","sha1":"957fc0c00b46ad7bde020ab09beb3bad61c8c1d1"},{"version":"26.4.0-alpha10","sha1":"fafc7f017ec4aabd41a82fb6c483c112973f8885"},{"version":"26.4.0-beta01","sha1":"9ec6ee44c6eef1fd9e42d3db54073157a6970fc2"},{"version":"26.4.0-beta02","sha1":"438888283a3090d19ca74145505aa033bc7ce1af"},{"version":"26.4.0-beta03","sha1":"339bfab55048fdf77535d05de4ccd7dc15e12077"},{"version":"26.4.0-beta04","sha1":"ade91e64227deb4a2fedf0111de55a7d415c3336"},{"version":"26.4.0-beta05","sha1":"29a52fd2b02ead394bb634a7f3c95b6dfcdae5dd"},{"version":"26.4.0-rc01","sha1":"b35caed1f04c974a39b885f93b8a2807ab3b9fba"},{"version":"26.4.0-rc02","sha1":"8321f25b44c6ff8982e2f3ac897ecc9fcf90ebb7"},{"version":"26.4.0-rc03","sha1":"3ea2bc906b8cd91c8a1a5f59106059306915df5e"},{"version":"26.4.0","sha1":"d2a6342ff3ade0d112da581349e06a2cae4c438d"},{"version":"26.4.1","sha1":"5eb843b2c3e05332b9cc6dd9bdc22d1f675a0fae"},{"version":"26.4.2","sha1":"fa044b2b6dc63fab924b233f34d9094512a68aa2"},{"version":"26.5.0-alpha01","sha1":"f856c98415eee8bdf0c1ddb43b258f9df0f61a10"},{"version":"26.5.0-alpha02","sha1":"82cb015ec99a7dc2400db9bd75b372af3ad65a95"},{"version":"26.5.0-alpha03","sha1":"83bf5458b1f9da9a276c67c1a165584227581829"},{"version":"26.5.0-alpha04","sha1":"29f1355757122f06f53194be755f7f7ed745f283"},{"version":"26.5.0-alpha05","sha1":"67acabb7a4b01b2a0b251800c7f7189efbf34903"},{"version":"26.5.0-alpha06","sha1":"df3aa3e8035bd77967bf52205eda913f4add1433"},{"version":"26.5.0-alpha07","sha1":"f7bbcd548cacb4b25321149e7ef47f2957f58afa"},{"version":"26.5.0-alpha08","sha1":"d8b8e91c185018dd3046bda5cc7595da9eefec1d"},{"version":"26.5.0-alpha09","sha1":"574f055007227b32b320b23e32ecbf5a1d29b355"},{"version":"26.5.0-alpha10","sha1":"582336b6c413450f355d2d86f6f3429dd25b745"},{"version":"26.5.0-alpha11","sha1":"93fe6888fab94af9523d4eb489e0a08c630393d"},{"version":"26.5.0-alpha12","sha1":"dd9f4029cb5ae0735fc370f4e47e79eda6c29730"},{"version":"26.5.0-alpha13","sha1":"a825e908f3f4bae504ddc2ac3207fd7f9ae6450e"},{"version":"26.5.0-beta01","sha1":"480e6fe6ed01c56312a5d4f4426b278e3fed9324"},{"version":"26.5.0-beta02","sha1":"f4b1291e87494f19fd13253d7fc1dfd9af2f154b"},{"version":"26.5.0-beta03","sha1":"379f2c5c072131b92ff62e383e78282734e00e34"},{"version":"26.5.0-beta04","sha1":"481aef38457110f62b347e2758c0e47538cbe62b"},{"version":"26.5.0-beta05","sha1":"5b820f9bf5f5e947ac97becba2a15c90f42ec8e7"},{"version":"26.6.0-alpha01","sha1":"35e1bc2760e22d2f17dae807a847271f6b1f609a"},{"version":"26.6.0-alpha02","sha1":"51450c232f50ec4c12162fd7ca35b9004f5525c4"},{"version":"26.6.0-alpha03","sha1":"3d74bef7f405cbb08ae42eb9bf14657d9bb875eb"},{"version":"26.6.0-alpha04","sha1":"6ba9859e56cb492425008b13834bb43bfc5e0856"}]},{"package":"sdk-common","versions":[{"version":"26.0.0-alpha1","sha1":"c8fa0784d3121d7a07d108ef16eba99491e42c04"},{"version":"26.0.0-alpha2","sha1":"a3139b6a01fc41b5c53f202094ca6d15f682841e"},{"version":"26.0.0-alpha3","sha1":"82dae06090eee889758c1edd62482c5fbe5ae354"},{"version":"26.0.0-alpha4","sha1":"4450c78efe3dd1daeaa26466db40164d58d98281"},{"version":"26.0.0-alpha5","sha1":"47e64a2b5a9380a6435ab2b5dd6e6f92dd487d90"},{"version":"26.0.0-alpha6","sha1":"2dc154dd000c2c23cf47d458f19badc56d5dea64"},{"version":"26.0.0-alpha7","sha1":"443a516b78d35a97aa92f019989d600e152fafb7"},{"version":"26.0.0-alpha8","sha1":"7a344c886947352e4d13e7756e27d8159a8018fb"},{"version":"26.0.0-alpha9","sha1":"2cfce00be3027812c9139faf47e589719f69e0f5"},{"version":"26.0.0-beta1","sha1":"e6ff40cb2e66a5735cd62be89cce450f34904cbf"},{"version":"26.0.0-beta2","sha1":"b8562571f8a1b2f9379ce3b379ac3c2d7e1000e1"},{"version":"26.0.0-beta3","sha1":"16e21d8b14e9500993eafbc3a05e5b5232c50a2f"},{"version":"26.0.0-beta4","sha1":"1658f31fad308d418c8f5458f06cd917ceb10b8b"},{"version":"26.0.0-beta5","sha1":"a8bfb165eca9b95188bb4bce963350a21b4ef492"},{"version":"26.0.0-beta6","sha1":"c233ddf6b2083864030f921bcfe0cfc882d44ca7"},{"version":"26.0.0-beta7","sha1":"7bf184fbdeaf71adadc20b3264a25ea7e7f543e6"},{"version":"26.0.0-rc1","sha1":"1b548ee19a4c807a1f4bd766194c372c0b29673c"},{"version":"26.0.0-rc2","sha1":"6246b426aac4a2d5d69cef816cdd7831b334428e"},{"version":"26.0.0","sha1":"3cf21b0652068a57852663610e8c7f0a044dc700"},{"version":"26.0.1","sha1":"9ddd2fa552e7b49548c421c0be4e65dc9a245f69"},{"version":"26.1.0-alpha01","sha1":"dd9f359c5ce5753b239a9fd26e1ff4017c87d03e"},{"version":"26.1.0-alpha02","sha1":"dea742b978e94b8a4b656c95a769fa16d8edeaea"},{"version":"26.1.0-alpha03","sha1":"46576c56039f8e1d330242a7d6ba7bd9d10945bb"},{"version":"26.1.0-alpha04","sha1":"22dd93f572ce8fdc888386f19c017a58a2fea33b"},{"version":"26.1.0-alpha05","sha1":"8582159f764255fc89f1300c31b1ffc9bbdeacf8"},{"version":"26.1.0-alpha06","sha1":"a152805b4937b74aabe18d2408617c086f90922"},{"version":"26.1.0-alpha07","sha1":"e4bcf1e03969402ffb5fcdd2e2225cbdc4d5aa19"},{"version":"26.1.0-alpha08","sha1":"5fe88b4441d596c9480b9ea86ede5d9cf4ff2e59"},{"version":"26.1.0-alpha09","sha1":"a2bdd51cdc7b42cf505f0cca667eb3900fe97bae"},{"version":"26.1.0-beta1","sha1":"55ce68ab07a263f4636db4a0ff5b31d73bd53a7a"},{"version":"26.1.0-beta2","sha1":"aaab7ca81d683817bb3e843be69ced25533e5f7b"},{"version":"26.1.0-beta3","sha1":"747090dbff34ed551e2164d606f5bc1140cffe67"},{"version":"26.1.0-beta4","sha1":"45d78cf91209019ff9aaa6695c95c61cbe1aa584"},{"version":"26.1.0-rc01","sha1":"faa6d938ffadf3953b78a1f34133b606b1436902"},{"version":"26.1.0-rc02","sha1":"5197f8fd32ac916def9107c5a97d3441ff775640"},{"version":"26.1.0-rc03","sha1":"c49074731ddb958272d7621bb442610e374efacf"},{"version":"26.1.0","sha1":"be76d44fd3b75e09b6487d72a1211d7ab23bb157"},{"version":"26.1.1","sha1":"ea7b2e3f17491e0c05945431127334d79d3b35f9"},{"version":"26.1.2","sha1":"2cf773af3fb0e1bbd56a80fc6903a9d2a40a248"},{"version":"26.1.3","sha1":"9f54dc681494c0cc10b7a992f90288f4c06019bf"},{"version":"26.1.4","sha1":"5dbdefb2dc6cb5ba1b4b059bf11c964a830c5755"},{"version":"26.2.0-alpha01","sha1":"b88833bb1cea9c4abb316e1746c12f806f36bcc6"},{"version":"26.2.0-alpha02","sha1":"7f27de6dbcfc8e2dd10c11d50db15663786d0662"},{"version":"26.2.0-alpha03","sha1":"7dfae01da4455568dcd02482a3274c413db1e49d"},{"version":"26.2.0-alpha04","sha1":"7fe7ba3edef0a56ae1c1a9afdd0c95e23933c60f"},{"version":"26.2.0-alpha05","sha1":"1e3f5e40367b8f230771f66536bce1ed47e1aa3f"},{"version":"26.2.0-alpha06","sha1":"61ed00d572b0f328a7ce8fc5124d416fd7b005"},{"version":"26.2.0-alpha07","sha1":"f71676afd3bb1de3583b74b8cf0729a3259cbc4b"},{"version":"26.2.0-alpha08","sha1":"dd0703e888acaecf1f1cb4c79773b79c40b926de"},{"version":"26.2.0-alpha09","sha1":"8e0dcd0ceeb06875e5f76f6f48d848d42ec0baf9"},{"version":"26.2.0-alpha10","sha1":"53955c8f05653e0389db7a20bef56735ed580c6d"},{"version":"26.2.0-alpha11","sha1":"e717f26e42c42c145db0351d7b463ab2775bc931"},{"version":"26.2.0-alpha12","sha1":"8d3a83d838fc07d9d13d4e3f56f32580129e4c70"},{"version":"26.2.0-alpha13","sha1":"7cd6073030e0228c67d5a894af1d540389107860"},{"version":"26.2.0-alpha14","sha1":"bd8846b8e6c5153c98f6e2fa2c419697a285d2de"},{"version":"26.2.0-alpha15","sha1":"48a36ba5e3beb4bd9e0f90b8e814d5d762b30cd0"},{"version":"26.2.0-alpha16","sha1":"40f4896074377564f656f5b09d031880990994dc"},{"version":"26.2.0-alpha17","sha1":"bbd37b2f9af711e8f8b7d205fc30df44a9cfa9b7"},{"version":"26.2.0-alpha18","sha1":"4bd23c489e3c36dd4e9c0d4bb7942c0e26656840"},{"version":"26.2.0-beta01","sha1":"7bc966b370341c4b25a9706424f9626da6ce26d7"},{"version":"26.2.0-beta02","sha1":"14dce1235c251db400df09bac29ff1f941556783"},{"version":"26.2.0-beta03","sha1":"10068d8d97a28b124ef4cfaed3b0c4b06be6c8c3"},{"version":"26.2.0-beta04","sha1":"832c206cfd9ea1cda53b1fbd7905ba26ff39e0a9"},{"version":"26.2.0-beta05","sha1":"da236da864caee8b9e7f601ad714d7d9b90ca794"},{"version":"26.2.0-rc01","sha1":"4dddb4142a77f412c5d16b373aa5fe3bed2a4cae"},{"version":"26.2.0-rc02","sha1":"10eb948ca904d57260d0b4bf9012d6d7f8b15224"},{"version":"26.2.0-rc03","sha1":"93edeaaf88ddb3ecaded6a5dfc2bc1438cbee506"},{"version":"26.2.0","sha1":"643a4b34b8dcfef3fce82f79fef298e4cc771d01"},{"version":"26.2.1","sha1":"bcc236baac9d05aee927370ccaf5785d92ed5e2b"},{"version":"26.3.0-alpha01","sha1":"bd1a7a8422f113bb7062393883b982bfaab29f6c"},{"version":"26.3.0-alpha02","sha1":"a088fcab18361748927061107f2c5bbe96df31fc"},{"version":"26.3.0-alpha03","sha1":"51307db1691ccf9c17ca3f664dcab7a15661df2c"},{"version":"26.3.0-alpha04","sha1":"a3c5153efcb9003bad88c89439ce472013797bb"},{"version":"26.3.0-alpha05","sha1":"264e2d914bf256b5af3d4b68a4e01001489b9a72"},{"version":"26.3.0-alpha06","sha1":"6f42709374534798f585c58b1bebbb9e5a2564a4"},{"version":"26.3.0-alpha07","sha1":"ccfdb954fd5809ca0ef2e771556a5f2962a10c30"},{"version":"26.3.0-alpha08","sha1":"d2e39a2bb27e8e1addb076223cf7fd3f3650244a"},{"version":"26.3.0-alpha09","sha1":"d04b318865504b01c82c57981929352d0f718e5a"},{"version":"26.3.0-alpha10","sha1":"47459445e51b470b735543d16ba046a96855174f"},{"version":"26.3.0-alpha11","sha1":"410e0aa1c97afeb7f395b120c106e849f78a4f0"},{"version":"26.3.0-alpha12","sha1":"9c984ad8aa63e2d5c1cf2bee2e9c5719d4fc23c"},{"version":"26.3.0-alpha13","sha1":"e4d0147be55c55fd7fef541af10752fa71b43194"},{"version":"26.3.0-beta01","sha1":"f2b85d20a7cb43ebc3e1e73c5606e0f5580898fe"},{"version":"26.3.0-beta02","sha1":"aad23857239cc003ed9fe9febf5415304cc97fb8"},{"version":"26.3.0-beta03","sha1":"dcc4e3514418e465cfbb0c773dfe17b3b636aff3"},{"version":"26.3.0-beta04","sha1":"362bb20ad56b1e001762d08558fb014ae1adc1d7"},{"version":"26.3.0-rc01","sha1":"44e02a7371a146543c528f459461794e78ab0e9b"},{"version":"26.3.0-rc02","sha1":"36ff0218517d7cd1b17b315d5d5580f03b36bcf8"},{"version":"26.3.0-rc03","sha1":"b2fce3f70a24f1287295a05fa54a5911b4719557"},{"version":"26.3.0","sha1":"bf44257049a0558550e2b4ae84ac38d2a9ed6776"},{"version":"26.3.1","sha1":"c9bed0f05024f919fe6168ef381afbe72074a272"},{"version":"26.3.2","sha1":"2a957be276c98be9f72dfff8c8fc2b2e349d6aff"},{"version":"26.4.0-alpha01","sha1":"9d5067ebf2d2367869ba17f16b8fea986101f5d"},{"version":"26.4.0-alpha02","sha1":"963aa0a5f75b8b39a536e7550fa30ecafb8d2546"},{"version":"26.4.0-alpha03","sha1":"bc3f53fb184b18fbe8e8321ad0d6ff6e896f3bcc"},{"version":"26.4.0-alpha04","sha1":"a6b059360c0e35a76277063050c5b8fe080f6c9f"},{"version":"26.4.0-alpha05","sha1":"7ee6dfc008949f4248e584a315195cf3f33ad17"},{"version":"26.4.0-alpha06","sha1":"294cfee6e4283e07937576b54b200b7c1450ecfd"},{"version":"26.4.0-alpha07","sha1":"8a27a5e4ed639b58e1d36f6dfdf11287b8dc8a86"},{"version":"26.4.0-alpha08","sha1":"69555c358d96d66a6e1e34bf3a2d0f3fe238ee38"},{"version":"26.4.0-alpha09","sha1":"468176378b1c4aeea4165f31a71d8ba0dc24a507"},{"version":"26.4.0-alpha10","sha1":"138e8a9179bc388653364d37be42dffa85dd198e"},{"version":"26.4.0-beta01","sha1":"d31f55bc4b60bfafb9fa929813d6bb84f795118e"},{"version":"26.4.0-beta02","sha1":"9afb391bcd293255b4c271535f00dde755e4ec92"},{"version":"26.4.0-beta03","sha1":"5708b96be839de2387bf044640942adc3f4b0000"},{"version":"26.4.0-beta04","sha1":"7a5f2458eb2fcc36eb9992ae7e682ef2ec8eee45"},{"version":"26.4.0-beta05","sha1":"73bbdcac66c04192b5f427ec0e374e3d5d610ce4"},{"version":"26.4.0-rc01","sha1":"b29aeb6c80afd03a808c5e21e2ee08a5f19ddd61"},{"version":"26.4.0-rc02","sha1":"8e8845df6edbc539b9b78d075e86d19495d5f10d"},{"version":"26.4.0-rc03","sha1":"664139460a24e6f962f51bfc29b01328fc9a2fbe"},{"version":"26.4.0","sha1":"7cda5b1442edfbd2f81c0b525f80d72ef960bc07"},{"version":"26.4.1","sha1":"c1bff8a9ff4684cce461d28fa0ff6380bdad8aa6"},{"version":"26.4.2","sha1":"963f5d17b9e60a13b5f345c8a3e001551f2f4771"},{"version":"26.5.0-alpha01","sha1":"bfa78fe41765e7d9d511f1efc44eaa08dbd33b2d"},{"version":"26.5.0-alpha02","sha1":"7c587bcc5fd173179e00a04db4b496353f80ba16"},{"version":"26.5.0-alpha03","sha1":"788f6c05d879ca3702f28fe5a529fb7623f14027"},{"version":"26.5.0-alpha04","sha1":"e6ced6dca22f1b1923caab51d42c526409d9e856"},{"version":"26.5.0-alpha05","sha1":"6118b385bdb3cb75dd6b846604d9e58528ed918b"},{"version":"26.5.0-alpha06","sha1":"21d497b8d6231bd6ef735675420be411abb450e2"},{"version":"26.5.0-alpha07","sha1":"5a9e152b706861745c56987117bbf1090922f926"},{"version":"26.5.0-alpha08","sha1":"ab67ad2836a0b3cb869a929a0f4532e719832d1c"},{"version":"26.5.0-alpha09","sha1":"6319d6d851eb92ed6d4a5de589408ced99d27c32"},{"version":"26.5.0-alpha10","sha1":"4e42b184f46770c9b547e1f67326843febc66dd8"},{"version":"26.5.0-alpha11","sha1":"f36d6beab8a99369c817765dfc30f769a5faeb68"},{"version":"26.5.0-alpha12","sha1":"2b2bdc951caac8a8071d6c3f4dbf2f7c5d0e92b3"},{"version":"26.5.0-alpha13","sha1":"6e1714910c1024d177b3bdb3b5410bc0c6f20159"},{"version":"26.5.0-beta01","sha1":"c2bf09156648299d069b1ea90384349d11ee5bac"},{"version":"26.5.0-beta02","sha1":"c7ff4410a120d18746e8c0fd817c9a4f9271c240"},{"version":"26.5.0-beta03","sha1":"ebf721d84aaac8ffa9fbdeaf2b1a3c34ea10280c"},{"version":"26.5.0-beta04","sha1":"17aa2a667cb21b869c9af512ad5463642311061c"},{"version":"26.5.0-beta05","sha1":"bc577e60f2ba5eea7aac811863420d91a638f1b8"},{"version":"26.6.0-alpha01","sha1":"2c4671d96d69a6bd6edcc45833cea814874f9cb3"},{"version":"26.6.0-alpha02","sha1":"48297ddb95b025deaddc8fc70a9864acea0dcf89"},{"version":"26.6.0-alpha03","sha1":"9ebd0a49d889aaaa2f908b289f503e5167d78647"},{"version":"26.6.0-alpha04","sha1":"1608ecf5f0b5c5a5855ad74bf36b6d07520734aa"}]},{"package":"testutils","versions":[{"version":"26.0.0-alpha1","sha1":"93799c3fb11805024024c4e90ede19c19d0954ce"},{"version":"26.0.0-alpha2","sha1":"fe4e72d810944e852e27bfb3b80ece0798aee46d"},{"version":"26.0.0-alpha3","sha1":"52e63d9513ac6d09e127a143651ab76c882ab55e"},{"version":"26.0.0-alpha4","sha1":"d4784e4333866b607ad04316a45a4b22c1705fde"},{"version":"26.0.0-alpha5","sha1":"cc48bb692d90044d81a66ee10d0731e24612fb64"},{"version":"26.0.0-alpha6","sha1":"aef2a3bb34337913d9ed2c577b37716f1660ca97"},{"version":"26.0.0-alpha7","sha1":"7f00dd1f2807c986a64f020144eb881ac8aa312d"},{"version":"26.0.0-alpha8","sha1":"d4ca15a6c003f72c4e00863d4906aa6b5e4b9842"},{"version":"26.0.0-alpha9","sha1":"d6ae3c8a4454d9986ce469414791974b56cd4104"},{"version":"26.0.0-beta1","sha1":"7a8ab7e655dcead7fab72ad83867d5872dea5ff3"},{"version":"26.0.0-beta2","sha1":"21c58b04d6f044f448cf03f4bc59f9051c59a4fb"},{"version":"26.0.0-beta3","sha1":"62a3916da0ef10ef28d6cbd5c077961ad4ac0513"},{"version":"26.0.0-beta4","sha1":"aece3e9873fd366a3224d5ec9a5a4269bd321fbb"},{"version":"26.0.0-beta5","sha1":"5805ff88d785b863030a495761dd26290d1ab002"},{"version":"26.0.0-beta6","sha1":"5817ca7a94bde0bce44348e87019944ac3221d91"},{"version":"26.0.0-beta7","sha1":"b6f3ad09516732b4b594fac1b8110b5e147598a7"},{"version":"26.0.0-rc1","sha1":"1f6a678f0e169db0fb64dcda7612bb975197eee3"},{"version":"26.0.0-rc2","sha1":"10ef90e15fc5b72332bbd9cc2e7c8091d40bdb49"},{"version":"26.0.0","sha1":"b62dd4440e470da8c04bfc803dd8c157ce27e74c"},{"version":"26.0.1","sha1":"f32583784653864515950d918f4aa9cd12a6894e"},{"version":"26.1.0-alpha01","sha1":"1574adf3a66595684068ec8f0b491ee12f5b9f0d"},{"version":"26.1.0-alpha02","sha1":"4b3bf6dcf3df8977b8fb98416a9e91e4e063cdca"},{"version":"26.1.0-alpha03","sha1":"72aa62fab11539446c74b2dc92cee81249aa8be2"},{"version":"26.1.0-alpha04","sha1":"f76d672c69fe01642f50125dc7048d3c23819acf"},{"version":"26.1.0-alpha05","sha1":"396b684e2bb6ee5ae120fc6422c9a903ffd03150"},{"version":"26.1.0-alpha06","sha1":"1187e7090a703fdcf397b053584814e12cde3fb"},{"version":"26.1.0-alpha07","sha1":"d9413edbe4fa1fea69797548b485d92705fc357"},{"version":"26.1.0-alpha08","sha1":"dcfc62f3f92bd4107f1d325f75f05fdeabccb65f"},{"version":"26.1.0-alpha09","sha1":"4f2767454c2a5951dc18bcf09138f80e19342bf0"},{"version":"26.1.0-beta1","sha1":"3132d5d654e6f96f0cb931f33b76ce9464517389"},{"version":"26.1.0-beta2","sha1":"426d1963922ffe3d8499d2ea1cd6a1c9bd9f4eaa"},{"version":"26.1.0-beta3","sha1":"e837020946b21315d68c8005b7d0a285555c9798"},{"version":"26.1.0-beta4","sha1":"cad2f98a39eb28113f2325859e4c6e4943e947a7"},{"version":"26.1.0-rc01","sha1":"56eb029b1bd3382ebcf13194b5e9c37953c47a23"},{"version":"26.1.0-rc02","sha1":"958ba1a0417fd2fe4c1b92d5c72cbb1f2e15e1a1"},{"version":"26.1.0-rc03","sha1":"66de1dc796d02feb48b1f540950f8e0951975e7a"},{"version":"26.1.0","sha1":"2d1830965f83f33edcd423022ddd9e7c99f86336"},{"version":"26.1.1","sha1":"a0ff6661d26730187175d3d8afa9aaf0d8b8c29"},{"version":"26.1.2","sha1":"f3c762f8b097c33511d5dd314d5d2bbfa98204e3"},{"version":"26.1.3","sha1":"51fbbb9ee384aadfd6ed1fc9b0bdb7eb8c0ba678"},{"version":"26.1.4","sha1":"9d881392033f4a71baf38c8df13ce697f006897c"},{"version":"26.2.0-alpha01","sha1":"429040b3d096ed8ed68112472cb95e6927b45d4f"},{"version":"26.2.0-alpha02","sha1":"ac6d10866cd2341d3f25c6fdc3d54fc9ee7c6419"},{"version":"26.2.0-alpha03","sha1":"70325b429f80c39c7d39656cb125745a0ceddaf"},{"version":"26.2.0-alpha04","sha1":"841c9a3f6ef10fb5dc230e72d9e227ccaadf2da"},{"version":"26.2.0-alpha05","sha1":"874719e41c8cd3faadcdaafaa8b71627a07107ce"},{"version":"26.2.0-alpha06","sha1":"1682dda9b095a98f17b1571dff64c5a939a41b34"},{"version":"26.2.0-alpha07","sha1":"8547e47c94aa916c009742a1ae5af0b79c19d3e8"},{"version":"26.2.0-alpha08","sha1":"f9caeef7b1cf2f753c327b60b8d2e9716fafd013"},{"version":"26.2.0-alpha09","sha1":"7a4deee8b8ce477e517ee3dfff371169f76f3e3f"},{"version":"26.2.0-alpha10","sha1":"37a7446d8935d304af84f3a488ec4f48e7f98a1a"},{"version":"26.2.0-alpha11","sha1":"5babccb58b75a2ffd68a6557e0226cff78c06fee"},{"version":"26.2.0-alpha12","sha1":"5f32709f8b603928c767cb2d8fe5e6008fbe9883"},{"version":"26.2.0-alpha13","sha1":"a70a7c2f0ee1460bc3868fb7f475ebdbc939d5e"},{"version":"26.2.0-alpha14","sha1":"2fad3ebc2d2c742bd00e6bdd30d93bc19ea1ab82"},{"version":"26.2.0-alpha15","sha1":"d1a4180c04ac22b0c0bb3875678cbf06c9009fca"},{"version":"26.2.0-alpha16","sha1":"e210a91a17ce2f50cd242d10808c3bb584e34a31"},{"version":"26.2.0-alpha17","sha1":"ae3d4120c8d60f1a5be63925ae0a04fbca300cac"},{"version":"26.2.0-alpha18","sha1":"e61fb98c6625d659a81cf36c35f633eefaa73c16"},{"version":"26.2.0-beta01","sha1":"fce18855a03a725e74e13259f31fd73661d35953"},{"version":"26.2.0-beta02","sha1":"cdf345abf9cc97718c5218d983eb46b5397cd621"},{"version":"26.2.0-beta03","sha1":"cf47a0e9f8a59ea403e34d81650d30922499943a"},{"version":"26.2.0-beta04","sha1":"6e52a3affc8236103c94ce38cded1ac60176e617"},{"version":"26.2.0-beta05","sha1":"63cc890a703657591ebcb5a864f5d9a77d2ff792"},{"version":"26.2.0-rc01","sha1":"1a1a703fc7286e18af13ff1584e4bc23d378c3ca"},{"version":"26.2.0-rc02","sha1":"42ddae8e04b0760e69fe2952871a3099e269f163"},{"version":"26.2.0-rc03","sha1":"6edfc506e8aaa3a530f9f2804cd24dd0d808be7f"},{"version":"26.2.0","sha1":"bfcf412c6911586fa129bc17d2fba7f643182216"},{"version":"26.2.1","sha1":"187fce377aae666ae2a11e07f927281e675a4a52"},{"version":"26.3.0-alpha01","sha1":"1ff9000e88095e78da65f00a3c5aecbf0b3bd859"},{"version":"26.3.0-alpha02","sha1":"b1ff88a04fda968ccad3e37c7b1b56c6c1e43e09"},{"version":"26.3.0-alpha03","sha1":"23a45a0ec6dbfc294dd44250237ff2ae37ad537d"},{"version":"26.3.0-alpha04","sha1":"657bcd57c25b6ed1058e8e0fddb452d79959e79b"},{"version":"26.3.0-alpha05","sha1":"50f5ac53600cad1ec005295f53726e7f71f2f13d"},{"version":"26.3.0-alpha06","sha1":"aa517840c326253f551c248611af10fb1b292a85"},{"version":"26.3.0-alpha07","sha1":"387b30d4ea222b207a770f71eee6dcafdfcec059"},{"version":"26.3.0-alpha08","sha1":"73516b8a7bf744b9f0037e5d35fb00c210a5b38d"},{"version":"26.3.0-alpha09","sha1":"3278329970bf7dc2b7a18ebef78664867ca162a"},{"version":"26.3.0-alpha10","sha1":"cc6912f72e8348d8edc4d42f72de2d8d4d5d44fe"},{"version":"26.3.0-alpha11","sha1":"a0f8c3fd02c880db2a77aeee7fb9b0a4fc421af8"},{"version":"26.3.0-alpha12","sha1":"9757f3343848fb227af6a42f19462624c89e75bb"},{"version":"26.3.0-alpha13","sha1":"8ee867136752ab58ddd0cae59631e390b6b9070f"},{"version":"26.3.0-beta01","sha1":"b6d9473510abd14acc115d108bb09280d74f6d4b"},{"version":"26.3.0-beta02","sha1":"3ca824fd4ce9723060bb3ee0efcb2634361a7961"},{"version":"26.3.0-beta03","sha1":"1edbb88af8a21621bd305973e8545b9897cdd9ba"},{"version":"26.3.0-beta04","sha1":"490c6c5c1b65234ff1d74a97c696a4574548f3b5"},{"version":"26.3.0-rc01","sha1":"ef3f06ea4d591fea93d99ef3e5cfa1239e9d7845"},{"version":"26.3.0-rc02","sha1":"29907060b5835263b3f65b4aed67ef69639eb17f"},{"version":"26.3.0-rc03","sha1":"11f32f5812ee1628cb46d959e4991d83b641d85f"},{"version":"26.3.0","sha1":"783f6a75841d0c7d6590440eb0b5bea7bcb419a5"},{"version":"26.3.1","sha1":"13b4c5b1f73d75bc84a269c2a5e94193d7fc7f01"},{"version":"26.3.2","sha1":"f84d5cbe2a8b7c02911e193d21540aca72fbe6d1"},{"version":"26.4.0-alpha01","sha1":"e0c799738c8367208e85c69c836e23d26495e8f3"},{"version":"26.4.0-alpha02","sha1":"bcccdc09d27558c711952e92345472f2452f8db6"},{"version":"26.4.0-alpha03","sha1":"96cbad848ebbc7d7cf5b08e75731f1333e94d65f"},{"version":"26.4.0-alpha04","sha1":"ea8e66fe3260f1e3e4fd1879fcaf2002ad1ccbfc"},{"version":"26.4.0-alpha05","sha1":"7fa3211178fe1d93f5a872cac90cff014d8a66"},{"version":"26.4.0-alpha06","sha1":"1b630d862e518500acb651fd1025a43fd91c8819"},{"version":"26.4.0-alpha07","sha1":"3acce14d2a46641b8adfd24535ce1f2cc544c72c"},{"version":"26.4.0-alpha08","sha1":"af2387a97101456b05ea5f28b1ab2ed2fbbb446d"},{"version":"26.4.0-alpha09","sha1":"f027e3d305726ee70b71c6e3d29eee37ebba5c19"},{"version":"26.4.0-alpha10","sha1":"783b3b6adf015e22aeb2c35b5abe14df0a3d138"},{"version":"26.4.0-beta01","sha1":"ee7f19dad352953d376bc6153d773651ee2b40ed"},{"version":"26.4.0-beta02","sha1":"51611396bbee6256e45e7daead87485f3622be9a"},{"version":"26.4.0-beta03","sha1":"c247ed2573a3eb02058ca4c54d5942b3956f5274"},{"version":"26.4.0-beta04","sha1":"661df5010a8d5ff4745b7c257d94597372bec1af"},{"version":"26.4.0-beta05","sha1":"fd1737bd8c8ba0e3bf34afdf122075c2f6bfbf71"},{"version":"26.4.0-rc01","sha1":"1eb0d0a13bba8d2c21ff4a6833150e223f8c2a31"},{"version":"26.4.0-rc02","sha1":"8540278c8be6c1ef250a5a7aa3ae3e04cf61c948"},{"version":"26.4.0-rc03","sha1":"f2d71dc9c0631292b09821c8e56f82c31ebb25d7"},{"version":"26.4.0","sha1":"8c8e276cec292b0d7f9adb1ddd045f79605238c3"},{"version":"26.4.1","sha1":"72245bacf044b97900f43b594bbceaf5cb55f485"},{"version":"26.4.2","sha1":"697177223a98610f98ab8b022866c88a26406919"},{"version":"26.5.0-alpha01","sha1":"6a267e673a9af8ab855a270af99c37c8a79d1bb2"},{"version":"26.5.0-alpha02","sha1":"a0862f7e42efd12a76c7b9d223781ad4f5a52fb5"},{"version":"26.5.0-alpha03","sha1":"93acd3f51013d668d5f0b7ad0bc9f4386e5bb591"},{"version":"26.5.0-alpha04","sha1":"d9f2eb9a6c65e35f557409490fb9f63586db65b"},{"version":"26.5.0-alpha05","sha1":"d716f1a2a56f5100f83515f1c41bfc623166dce"},{"version":"26.5.0-alpha06","sha1":"da3c251f38492c6c794b83f9e517e238014ccd43"},{"version":"26.5.0-alpha07","sha1":"d63d5521850ab6bb9d08a82e3563bb86ac79dfe"},{"version":"26.5.0-alpha08","sha1":"ca5b7f42a8f67dac66722a6b51dd2b61e6be7245"},{"version":"26.5.0-alpha09","sha1":"ffc59e291828c590a120c7ac6bf6859a2343b34f"},{"version":"26.5.0-alpha10","sha1":"cd63831bcce9c43e313c62ff8a4819591dc58702"},{"version":"26.5.0-alpha11","sha1":"3bcc4cd5ca44d138610b53db1ff600c91126c772"},{"version":"26.5.0-alpha12","sha1":"f0dbe9b1bfc4a8cdff021f1834c060cf5799a510"},{"version":"26.5.0-alpha13","sha1":"c2c3481f729d15e7a1e67137e1880ad396fe235"},{"version":"26.5.0-beta01","sha1":"a748e6f9493c9227c1077e52a295bde823798038"},{"version":"26.5.0-beta02","sha1":"a92c63d9de6ef04918adc7705122c04d06f12b09"},{"version":"26.5.0-beta03","sha1":"57539bd68fc9cefe5cf32d7f6d2b7a8e88a2ed26"},{"version":"26.5.0-beta04","sha1":"d357ce193103566477f329d560a12f7640ff7d9e"},{"version":"26.5.0-beta05","sha1":"9f629d8767c020d7a606477f989df50bfe5da56d"},{"version":"26.6.0-alpha01","sha1":"25ee58bba14bcf665493c1c44841dfd319b180a"},{"version":"26.6.0-alpha02","sha1":"9981be0b6a957d95cf5ed3c8dfd7a59c0116c109"},{"version":"26.6.0-alpha03","sha1":"fca630d7e279d4d759cd770117dcfcb8fa48c3ec"},{"version":"26.6.0-alpha04","sha1":"7d2eb94cc86de7ed6527fe3959ec8739b7235057"}]},{"package":"common","versions":[{"version":"26.0.0-alpha1","sha1":"eb6afcf133a7f09d932314b5f6f649245db3ba7a"},{"version":"26.0.0-alpha2","sha1":"84db181bb03c26916f614b9249e89eab23b4a726"},{"version":"26.0.0-alpha3","sha1":"3a372b5a6f44f9d8734f0f17cfe8bfc6ce81a197"},{"version":"26.0.0-alpha4","sha1":"8ad436cde95a151e0dcaf4e2aae6efaa63cea46f"},{"version":"26.0.0-alpha5","sha1":"28be80ee5383d09aa5455c540ac84741580e902d"},{"version":"26.0.0-alpha6","sha1":"e3ecb860ff932f4bd39c12dac2a072753ffc41d4"},{"version":"26.0.0-alpha7","sha1":"a84a383e83ea1f2088d00aef7412d11fd74fd860"},{"version":"26.0.0-alpha8","sha1":"173cf138a80b5a3fce36ea98cf0ab57eb06eda16"},{"version":"26.0.0-alpha9","sha1":"f3b9c83539d55104e089f9deb106f0cd9db23b33"},{"version":"26.0.0-beta1","sha1":"3ba079d62e2fb9c6a5ff3146f2cabbb02a9f4666"},{"version":"26.0.0-beta2","sha1":"d48ee22ffae4d4433580d897fccd3f6a8c38db27"},{"version":"26.0.0-beta3","sha1":"7880ff7b10975423a9648d9f89286916c0802df1"},{"version":"26.0.0-beta4","sha1":"421c67719e2e85ecb16bdae90efeb223887500b5"},{"version":"26.0.0-beta5","sha1":"55201a46bbcbc11a99da88d2793b6d8cedca5a08"},{"version":"26.0.0-beta6","sha1":"ef5cc824fc1a69fb6ebf71d2804765633d3734bb"},{"version":"26.0.0-beta7","sha1":"b70213b6fd3dc41d7b838a768e4c535b4392a871"},{"version":"26.0.0-rc1","sha1":"450284d5a1b00c5e5afcfbdcf53682542f7aee52"},{"version":"26.0.0-rc2","sha1":"a2dd09e936f3e81a0a7e838cfe6cac5188d67d9f"},{"version":"26.0.0","sha1":"a75e1cbf30e55dd23f73801c32cd565b6eb5fb3f"},{"version":"26.0.1","sha1":"38e362a43a905b6745d609bea37faddb0c5bffab"},{"version":"26.1.0-alpha01","sha1":"89066a721e49419b6d20649b355f2a372feb0531"},{"version":"26.1.0-alpha02","sha1":"3a8726d17afa08207ac941f8505526d5d7b069be"},{"version":"26.1.0-alpha03","sha1":"e4670bcbab90f2fa516c4f65b200e69ddd27d71e"},{"version":"26.1.0-alpha04","sha1":"e818e6f4016ee105e940cf8f8dc96aac287d05d"},{"version":"26.1.0-alpha05","sha1":"2f595c391a38935e5a3035d2c0e83f0b58ce6f5c"},{"version":"26.1.0-alpha06","sha1":"670208532a41d1fda59cf3e89068fe5505d30850"},{"version":"26.1.0-alpha07","sha1":"c63485318e794c7f96b0d7f10d755ccd152ff2ed"},{"version":"26.1.0-alpha08","sha1":"315969bb433a25728f26a895a1de982974dea482"},{"version":"26.1.0-alpha09","sha1":"32d81087a314f6f3a472c387b014a85821a48435"},{"version":"26.1.0-beta1","sha1":"73cab258b9f5849fb1a8183fbe1c1386e6cc5a22"},{"version":"26.1.0-beta2","sha1":"308af3637a649b5dc0d0ff5919eeea1e0aca236e"},{"version":"26.1.0-beta3","sha1":"db059cc3236c2def147f7fb3543e08f5bc5b14c0"},{"version":"26.1.0-beta4","sha1":"c65c3791d44a875dab38b0fc88cf625578dd8add"},{"version":"26.1.0-rc01","sha1":"9d614580c74da1a7e1a002623aedc0143bb2432a"},{"version":"26.1.0-rc02","sha1":"358bedf950d37b81bb69ecb355d31c287d00869c"},{"version":"26.1.0-rc03","sha1":"27b10ab7086f59dff870c60c0a202801e48df6e4"},{"version":"26.1.0","sha1":"e1a8ac055f330fb0e7c8a4b5edecb578883b8bf3"},{"version":"26.1.1","sha1":"ba975dcf04fd201684cbf7acd8692e5b7a537f9d"},{"version":"26.1.2","sha1":"c31bbd68c51ed0ef3b8d7cdd5615acf762473887"},{"version":"26.1.3","sha1":"ee0a1ccb7a9a276c05a2b19e8ccb0c3fa9e03b72"},{"version":"26.1.4","sha1":"b70b6ef7188fe5a6ad6186f0efb22e536e483b9b"},{"version":"26.2.0-alpha01","sha1":"b0f2fac7eadaf0ea9fd7d36d15db2dd3d108340e"},{"version":"26.2.0-alpha02","sha1":"b3b2166104a5192ae5b2b7f1932f3893cc3b16ca"},{"version":"26.2.0-alpha03","sha1":"65f9e6566664d7828b3be32eb370e471f82e7770"},{"version":"26.2.0-alpha04","sha1":"1e0d244d222fcc1b1a95dc6a1b33dcb0f3c23fc2"},{"version":"26.2.0-alpha05","sha1":"fa0b13b94db01a821b45b0df63411b3e11a62952"},{"version":"26.2.0-alpha06","sha1":"d8f293c5a5f98207bf1fa6f0944f801417a5df87"},{"version":"26.2.0-alpha07","sha1":"23afff6732f189fac0c0600b7c1f027f400cf18b"},{"version":"26.2.0-alpha08","sha1":"9ed2d419cc1a9b01b9fbefb9fb7abf30cd5987b1"},{"version":"26.2.0-alpha09","sha1":"d709ad33001f88039991e02af0f8243e67336746"},{"version":"26.2.0-alpha10","sha1":"3efc319283528f52072df4e1473bb7aaa47aa701"},{"version":"26.2.0-alpha11","sha1":"90e334f109f795cc5ed434240995d1dc5c16aa79"},{"version":"26.2.0-alpha12","sha1":"3ae218c5ce95306af0e905ab72aee8a2a07fea6e"},{"version":"26.2.0-alpha13","sha1":"78cf79c641c3f2c5e4b5a42cd144b01fc1bd19b4"},{"version":"26.2.0-alpha14","sha1":"2610a61ac023a6fe7be1d507d9ee811d55ded6ee"},{"version":"26.2.0-alpha15","sha1":"80f31f9ca3e1e6d55906862151a4849743e418f0"},{"version":"26.2.0-alpha16","sha1":"14ff9cfd876d4e473e80c211b87034aa708c619f"},{"version":"26.2.0-alpha17","sha1":"5aabe2e038d36d9205ff2514abfdf84689920d6b"},{"version":"26.2.0-alpha18","sha1":"dd4f70b5fcfd365b88d781c3bf93f24da6466359"},{"version":"26.2.0-beta01","sha1":"4ddbf677d04543163f3bc6fb0c617877f2c72df8"},{"version":"26.2.0-beta02","sha1":"c1f82e3fe2417cc9422d7b0523478fd71fdb6082"},{"version":"26.2.0-beta03","sha1":"6771c4aa88d2595a3e25bab7664693be8b7f75b6"},{"version":"26.2.0-beta04","sha1":"6433e7bae23553bed598c0ebf0d392185c2fd068"},{"version":"26.2.0-beta05","sha1":"ffcaa355e9ea3b2bc29df24e7d8cf8831697cfc0"},{"version":"26.2.0-rc01","sha1":"105ce21f4d4b47bea32d0eafb72af0f00b1d2f0b"},{"version":"26.2.0-rc02","sha1":"eb95693b70d47d038c409e3ceacd62ceb5bc9c8f"},{"version":"26.2.0-rc03","sha1":"3ef0beaa54a3a06283d62222483619c710f04d45"},{"version":"26.2.0","sha1":"57958d19825a0f4d07d26522b960fdc577b530cd"},{"version":"26.2.1","sha1":"b1b9e1a6efa7b64cba484e9f6562b5f63bf23e22"},{"version":"26.3.0-alpha01","sha1":"33fba8e484ba041772bb795b2054e2acd6eefa0b"},{"version":"26.3.0-alpha02","sha1":"7b174a3388c3a303a533536a8e3cc7979a18cd49"},{"version":"26.3.0-alpha03","sha1":"6121db52806115aa88155a767eab40221b814739"},{"version":"26.3.0-alpha04","sha1":"25dda2cf0b31bb57e402b3fe963b7340afcab4d9"},{"version":"26.3.0-alpha05","sha1":"11aa4efdf1401c80edefd08b3c3c9c4f0b1ac224"},{"version":"26.3.0-alpha06","sha1":"4846fd561615012c5fc52da1272efa07faf05376"},{"version":"26.3.0-alpha07","sha1":"7c2fed7a76baa149c32df01467c8b924c0b0d286"},{"version":"26.3.0-alpha08","sha1":"8702c41dcd1ba53359a490d1b7ac3384fce0894c"},{"version":"26.3.0-alpha09","sha1":"e9f5c6e12a33bd8a579ef8faed62431ee9f40737"},{"version":"26.3.0-alpha10","sha1":"168509046bf868d79773fd168c1481ce89025c80"},{"version":"26.3.0-alpha11","sha1":"c332d1061acb74172cab86f374daf359ac88ea9d"},{"version":"26.3.0-alpha12","sha1":"ebf0011e16f2a9d81f78d4d511314d4d15353707"},{"version":"26.3.0-alpha13","sha1":"6bd3234ebcb187d83ec8912d92b0c8b312372745"},{"version":"26.3.0-beta01","sha1":"dc8a65ebcc8576c458b580227fd404bce4b88935"},{"version":"26.3.0-beta02","sha1":"a2cceeb4fa87d9dbc662d733bdf5826042352300"},{"version":"26.3.0-beta03","sha1":"19a6fc2b30e92475970c92febd06ec486563fec"},{"version":"26.3.0-beta04","sha1":"d1b289a1a7372f8dea283304999239607ff8bdbf"},{"version":"26.3.0-rc01","sha1":"6e5bc3773dfc81073a72a7e200cce2fe90ec5739"},{"version":"26.3.0-rc02","sha1":"2e8aa2d3eb46d0652f384e90131fe9827a28b205"},{"version":"26.3.0-rc03","sha1":"91bdf291afe8277fcfeed574c6f57729a3f360b9"},{"version":"26.3.0","sha1":"1d9b4db75bbe5fe357c8a56db506f2361ebd508d"},{"version":"26.3.1","sha1":"b0e3aa846dd94f9608018f7ef6501d1069f6488c"},{"version":"26.3.2","sha1":"adb16705cd516635d9c2267930c40216b965bf08"},{"version":"26.4.0-alpha01","sha1":"9a8735293ea3484dbcdd17813864f4b763e2797e"},{"version":"26.4.0-alpha02","sha1":"21632c34d6a9c18da90a80ab0ebc8c24a024e418"},{"version":"26.4.0-alpha03","sha1":"94e999bb19024c36b1025432efeede76fe47daba"},{"version":"26.4.0-alpha04","sha1":"4eeb46b72293f2c90a8999cbd7d6a6fddd2aaf38"},{"version":"26.4.0-alpha05","sha1":"9dca7d7e41f7c3852a7675b0737c937050efe4bb"},{"version":"26.4.0-alpha06","sha1":"7943768c33e176048f6aca6e489a02f0e4a52c3e"},{"version":"26.4.0-alpha07","sha1":"a94a859fcfc360aed9309e718bf67396aa2d3fec"},{"version":"26.4.0-alpha08","sha1":"7e973e74bc05d3db8ebcbbf46cbf9c5e9e150e2d"},{"version":"26.4.0-alpha09","sha1":"aa8f4a6dc1f76c88dfba371481993dde59544d15"},{"version":"26.4.0-alpha10","sha1":"e74bcdd063fe6a2e62cd69c4816a06c6803178ac"},{"version":"26.4.0-beta01","sha1":"c82e4f704516981e120e2e010ecd11a8de973e1a"},{"version":"26.4.0-beta02","sha1":"4c9e80f431d27768f1f3188041ca3a7a8fdb85a3"},{"version":"26.4.0-beta03","sha1":"30f4f6f0bbac056ea58b8716bc9add9fda66d068"},{"version":"26.4.0-beta04","sha1":"e2317089967f5d563dcbb4d61fe64499d7a52c75"},{"version":"26.4.0-beta05","sha1":"767e202546905a3b9114c02d92c515ab1ca683fc"},{"version":"26.4.0-rc01","sha1":"49198428f7a1bbc97a21c0aad804247ef48e6594"},{"version":"26.4.0-rc02","sha1":"d9cf66aba3ab587475cfb8d0f1f84344361e9e01"},{"version":"26.4.0-rc03","sha1":"8fcb9552f0e3d6dab21cb7fe96d91eac202d7139"},{"version":"26.4.0","sha1":"539939e284fba9fe343b890a6e21c9333767c886"},{"version":"26.4.1","sha1":"d14471b3a2c7c21a705c67980fae4d3d3a8ca4eb"},{"version":"26.4.2","sha1":"71f8d5ea1ddb6bfe3fb100e0642262951e11e304"},{"version":"26.5.0-alpha01","sha1":"d0fd8f9186e52c8e7ed7d6021e6bdefa54ee593"},{"version":"26.5.0-alpha02","sha1":"a952e19255082b0632bfee44c2b92f1917fe1062"},{"version":"26.5.0-alpha03","sha1":"dbb8a92ef888ba4c13079ddd9b3740454f990718"},{"version":"26.5.0-alpha04","sha1":"6ed1554ba7d0f9e5bdf4fb9b819b3154621c02e"},{"version":"26.5.0-alpha05","sha1":"1c352217b4d0e64093b8592ad7f770bfc9553405"},{"version":"26.5.0-alpha06","sha1":"1739e6e89aa88c9aa48948c1c8b520f882566b5f"},{"version":"26.5.0-alpha07","sha1":"2dd39795f4f3b3d9c45a12b8e07776288b30a7b9"},{"version":"26.5.0-alpha08","sha1":"ab7f4a24ecee8c0535db438028b10e2b0410e89f"},{"version":"26.5.0-alpha09","sha1":"e1fa6f90b8c35e7713f2807dfd5e42191459ca20"},{"version":"26.5.0-alpha10","sha1":"85cd0014d8fd43bad6e86aee4eb2455879718199"},{"version":"26.5.0-alpha11","sha1":"e671351a90d14b002e36a162ea976eae5b945b13"},{"version":"26.5.0-alpha12","sha1":"574b104ca0e751923a755bbb2fcc5010b0445f08"},{"version":"26.5.0-alpha13","sha1":"6cd8f311afa5536f2c86622bb7a3316317ee8f7d"},{"version":"26.5.0-beta01","sha1":"fa295e0a8373bc9854ba7dae7374070fed887636"},{"version":"26.5.0-beta02","sha1":"34023cba075c2a28d9cb65a0b7cc3db7ec08b472"},{"version":"26.5.0-beta03","sha1":"f584e9de9f31ece14147b6c98d0d6805d9eeae4"},{"version":"26.5.0-beta04","sha1":"d8cbd418ec119e300010ddc393b2025f8082e82f"},{"version":"26.5.0-beta05","sha1":"2a772ba47b0cd2e477072b307f2bf2a59680243"},{"version":"26.6.0-alpha01","sha1":"15a925816d372a666264163a6db8c8cb69bedc54"},{"version":"26.6.0-alpha02","sha1":"194c264553c357820a3e0a9dbe776a6e81ab7251"},{"version":"26.6.0-alpha03","sha1":"df7eaba3715ba8a6dfcbb827009dba9a3dbc4600"},{"version":"26.6.0-alpha04","sha1":"6fea0d112e1e58d51a2077c5d78694fc7e2d817f"}]},{"package":"r8","versions":[{"version":"0.2.0-dev","sha1":"cce5f8a4b273482483dfd80b4a6dcd03c434e50d"},{"version":"0.2.1-dev","sha1":"3caeadd7b75eebbffbd48e343a7b3d2c71add9e3"},{"version":"1.0.10","sha1":"b6ea3952037133afc6291ed450c6499e3b97ff83"},{"version":"1.0.18","sha1":"5b6b97166983f63f78540dfb04123c79466a9a29"},{"version":"1.0.20","sha1":"b622bd098f64b9657ff4744c8a3a13c7abba1352"},{"version":"1.0.22","sha1":"233cdf65836799ff69e961e5e7c9d78d61f58f19"},{"version":"1.0.23","sha1":"21b318d63ef79320200a24e349cd67761b916f6c"},{"version":"1.0.25","sha1":"f49de530ddf66af62670724ce81cd7da84db2192"},{"version":"1.0.30","sha1":"6025ea9a7fbb22bedd917c6bbb32e0e4a92f41f6"},{"version":"1.0.31","sha1":"625e5a8c12070fd7474ca214d0705c6146b52014"},{"version":"1.0.32","sha1":"a25aeacb227742d2434e64724eb38f8ba01ccfb4"},{"version":"1.0.33","sha1":"711e23a8440a1c0ae057f23cebbb524001795fc0"},{"version":"1.0.35","sha1":"6f6cdd98aa49762a2148a631252fa87d1bce5d01"},{"version":"1.0.36","sha1":"3eebd04740c50d549b6d1bce96ea5d83c69c3c8b"},{"version":"1.0.37","sha1":"8bfa5beebf4c49b5187e8faaf6bb0b2adfc91534"},{"version":"1.2.48","sha1":"a5fc998e7b500135bf28e1bd95df9d456a818a31"},{"version":"1.2.50","sha1":"b51606c3f3b8aa880f51bfda848ef5db016444d7"},{"version":"1.2.51","sha1":"9e38d5c9bcbd1eb90c03b6546f3331cc52e35f98"},{"version":"1.2.52","sha1":"721308570f96231ec1dc0352a4129175ce94ffb8"},{"version":"1.3.52","sha1":"4b7f0a63f75930caebe075d35849eb4ea01c572d"},{"version":"1.4.93","sha1":"eb226c8ac5c41a697797fa9fd718828dfac801b0"}]},{"package":"draw9patch","versions":[{"version":"26.3.0","sha1":"796dde854bcba5297ab7cf1f5d5eb11009911537"},{"version":"26.3.1","sha1":"95e5e317a42a3b69e273161ea45ed5463532a615"},{"version":"26.3.2","sha1":"4488c91b6ef935fe74e516ad5b2b370596012b7e"},{"version":"26.4.0-alpha06","sha1":"4dc4fd03e9b6ddb1482c3b43176fcd0587109370"},{"version":"26.4.0-alpha07","sha1":"7d26c6743c40b1d8889764fe54c1c128a95b0b82"},{"version":"26.4.0-alpha08","sha1":"9302c214043449c7627efa71e14cdc5bae9134ad"},{"version":"26.4.0-alpha09","sha1":"966d5969882c437dc8344b0fcaabbdf64439bf7b"},{"version":"26.4.0-alpha10","sha1":"5be532aff5fd425bec8dc61d563f2fd4320e5393"},{"version":"26.4.0-beta01","sha1":"63c209134adffa70d1a3bc8570831f510b684235"},{"version":"26.4.0-beta02","sha1":"d5cdcfcd9916e938471d6db14563ee29e0f95ac8"},{"version":"26.4.0-beta03","sha1":"9f78cdabb20448eed922b1749dec068edf8a2da5"},{"version":"26.4.0-beta04","sha1":"b3d175e982c09f5cd6b9eb3f615d18515da75498"},{"version":"26.4.0-beta05","sha1":"38a02509c3cc4e2daa5b9b63ee625649dcbe3620"},{"version":"26.4.0-rc01","sha1":"ecf527dcb453d42e4812ada001e3c5f08ab51c0c"},{"version":"26.4.0-rc02","sha1":"33b589436b1bef70a465f17b7b775a443a3bc30a"},{"version":"26.4.0-rc03","sha1":"7906aa8342ce97ca61b6d723b08f13b5766e6f52"},{"version":"26.4.0","sha1":"45b1a6138ec39947a51b4111743e99db5e846c79"},{"version":"26.4.1","sha1":"c78498619e884b6f18ae2eda38a2e047af107f5e"},{"version":"26.4.2","sha1":"a8c24df4b36e3a7bdd7a5c0d2971ac594881656"},{"version":"26.5.0-alpha01","sha1":"23bc2d6e4516311d9914fb7ea898ac8ec4d851a0"},{"version":"26.5.0-alpha02","sha1":"473ba7705ea92df2b28a87442c86fddcd4317cd5"},{"version":"26.5.0-alpha03","sha1":"b4a96cd37c51a0e4f7d748c665349e0bd76bcda"},{"version":"26.5.0-alpha04","sha1":"e966c04a4941e0db2cd93f3a48f67b53b42335b4"},{"version":"26.5.0-alpha05","sha1":"bbb967a71b92facbf2f274f61e846963bf105bf5"},{"version":"26.5.0-alpha06","sha1":"a7f215d67231d822bce906408c651e6a587c93c3"},{"version":"26.5.0-alpha07","sha1":"4d1ac62ec414039cf86d00284c073a49d7072895"},{"version":"26.5.0-alpha08","sha1":"c150b306abe004c78fd3759e40c09b8f75909c94"},{"version":"26.5.0-alpha09","sha1":"787201331c567b9b76fc9c17af92e7d7362ae625"},{"version":"26.5.0-alpha10","sha1":"439bc530d9361b819ae501804830c53879739d9a"},{"version":"26.5.0-alpha11","sha1":"c54aa9c25873261d9afe40307de25c5323702f35"},{"version":"26.5.0-alpha12","sha1":"258bb3e95cc45966a6efb68b82c2ab65abbd6039"},{"version":"26.5.0-alpha13","sha1":"941e82745a8883df26ce82b086ecad948c3e7a87"},{"version":"26.5.0-beta01","sha1":"418e6b20b6888f7b5f35b5d39ef7e678e8ef1c08"},{"version":"26.5.0-beta02","sha1":"4c11065e8cd61f55ef8a3e91adf877b84eaecbbc"},{"version":"26.5.0-beta03","sha1":"ea62e221fd05595c81b004170108690fe2537eb8"},{"version":"26.5.0-beta04","sha1":"67348b9e40ec384048be4782ff62e3fcea240536"},{"version":"26.5.0-beta05","sha1":"f93190c8090165b5cbd42fa9feeb056dbbc1736b"},{"version":"26.6.0-alpha01","sha1":"b1a459959c699e294f2731af6c333e190ee1001b"},{"version":"26.6.0-alpha02","sha1":"f55740f307584393cf67a08c4f5c873eb531e4d"},{"version":"26.6.0-alpha03","sha1":"864847ed6c310363b7c9f663e30d07e3dd35e3a3"},{"version":"26.6.0-alpha04","sha1":"3f42ef129f5dcea11c897c8c59989efbd442138f"}]},{"package":"ninepatch","versions":[{"version":"26.3.0","sha1":"27cd8cf53c018ae438471a377ecfcbcc5b094350"},{"version":"26.3.1","sha1":"87d9431f156d7d7971706049a6ac16e5cda8b7c"},{"version":"26.3.2","sha1":"6a8f92b2fc97f60f5c8fcbe5ee5ab788f18f8643"},{"version":"26.4.0-alpha06","sha1":"d27ce71bcdad5fb555f1f4f596177c5d82864489"},{"version":"26.4.0-alpha07","sha1":"de873296c912628d73d148bcdd7020bcc9b85f97"},{"version":"26.4.0-alpha08","sha1":"cdf97bc21ed24c070e50a812fb48318ade8a3ad4"},{"version":"26.4.0-alpha09","sha1":"f013b8aa7f2a9cf6f6b116d3af2d17fd3b23161c"},{"version":"26.4.0-alpha10","sha1":"abcdfbd853024ecb58b390fad72f7758db9eea70"},{"version":"26.4.0-beta01","sha1":"4a16f47916c5c30b07f8de89c8a96c5b257ce24c"},{"version":"26.4.0-beta02","sha1":"fd41703e5ae04f5167a3c6ab49242995beb6785e"},{"version":"26.4.0-beta03","sha1":"f6ce4d04db810029f841ee0fd2ba1d705b3280c4"},{"version":"26.4.0-beta04","sha1":"867bc94a8eb5db1180613ac58dd4232fdf7c2909"},{"version":"26.4.0-beta05","sha1":"4db0027aa61396e71c486b5ed31950fbb90c40e6"},{"version":"26.4.0-rc01","sha1":"7904ee34d91a8fc9ab5668502251dae9ca167493"},{"version":"26.4.0-rc02","sha1":"4623c116c156e97abc7cc608defbf2b938cd504a"},{"version":"26.4.0-rc03","sha1":"4294a582377f049fc97cdbaade4c0a12fe1a2a49"},{"version":"26.4.0","sha1":"52b51a116b7bbaef09d24dcacc422c0601bc8755"},{"version":"26.4.1","sha1":"7d8f506f0b507955183842dd3078cb9fb0b45865"},{"version":"26.4.2","sha1":"7376edb27c1519fc965a2af14f9437b229594d19"},{"version":"26.5.0-alpha01","sha1":"815736ff794ca59602aa3e547574562a99a2c0a2"},{"version":"26.5.0-alpha02","sha1":"83dfce1b7ae83dac3ff87383fd665e9563717fae"},{"version":"26.5.0-alpha03","sha1":"9fd988f8798396d880a07f6e37be6d5cbc70b1fa"},{"version":"26.5.0-alpha04","sha1":"1c41a8ef4b016fc2f327ce000e64495cf38070a7"},{"version":"26.5.0-alpha05","sha1":"cf8456e5aeeb123662e81888d9452b318b6b98fe"},{"version":"26.5.0-alpha06","sha1":"29fb703cd8f15d81f24e359126ed0809f6963196"},{"version":"26.5.0-alpha07","sha1":"45a209d13a9ca21a891d914007af4f858e9c6660"},{"version":"26.5.0-alpha08","sha1":"9ffcb5a0e93d758c575d7eea35b85abe0a57443"},{"version":"26.5.0-alpha09","sha1":"3b7cca115dc106707a373d059205ed2040469240"},{"version":"26.5.0-alpha10","sha1":"39aa0a23221359fa02d124ce0bd5f526d0542cb4"},{"version":"26.5.0-alpha11","sha1":"fe5e5dd000b0893d3f74506dfdd4cb7e4efd56a9"},{"version":"26.5.0-alpha12","sha1":"5f5d0dd21426a79b230cf51b159a7941806abb74"},{"version":"26.5.0-alpha13","sha1":"68f2c495f3f4cdb71201b0690dbac64fca2e49dc"},{"version":"26.5.0-beta01","sha1":"99599c39440107d97e7ce79686fdf9ca2530e6ee"},{"version":"26.5.0-beta02","sha1":"360eb876c14e767005cd3bdc95111ad335426e64"},{"version":"26.5.0-beta03","sha1":"63280d0e35871726a226af9236a49dfef6a9ef8a"},{"version":"26.5.0-beta04","sha1":"36e4c28703b7fe86d59ba57cf8bc09313c59a868"},{"version":"26.5.0-beta05","sha1":"aaade45e91c999d9c267bbe35acfacf40c4cd31d"},{"version":"26.6.0-alpha01","sha1":"b39f104482a7f0e8a78889630c0ef249e2e7dca4"},{"version":"26.6.0-alpha02","sha1":"f6a15451e9802706bca16ff96461037daab29ba4"},{"version":"26.6.0-alpha03","sha1":"5e9ce8e0443fc6d7fa593ba09301d5300dbe57aa"},{"version":"26.6.0-alpha04","sha1":"dc5189eda28723131289ce1fd93c5f119311dc7a"}]}]},{"group":"com.android.tools.layoutlib","update_time":-1,"packages":[{"package":"layoutlib-api","versions":[{"version":"26.0.0-alpha1","sha1":"c6305850b60b69e59c799bffd39245d30a95821a"},{"version":"26.0.0-alpha2","sha1":"dba50a2a8ea4473815814390657ecc263663da65"},{"version":"26.0.0-alpha3","sha1":"4a28d0fdaa746810fc56f672a67e6d80ec3bd909"},{"version":"26.0.0-alpha4","sha1":"38a12a494b073ed4fb6752f910e7a7e27a4cd652"},{"version":"26.0.0-alpha5","sha1":"8614f6a5ba212b553115962be3cdebd4b2bbabb"},{"version":"26.0.0-alpha6","sha1":"387fa109c378685171771b0eecfe4a5b4c7beadd"},{"version":"26.0.0-alpha7","sha1":"2efe7a033c33e98c02751c11a32e4479720e44b0"},{"version":"26.0.0-alpha8","sha1":"ea696ead7cfc9d652a3ae5a7ae09028d844b3cce"},{"version":"26.0.0-alpha9","sha1":"c079e30165a9789505bcd846154fcd5a88cc522f"},{"version":"26.0.0-beta1","sha1":"be1880e19d239962123bd17cd83ed99e49b7e9be"},{"version":"26.0.0-beta2","sha1":"6fa629dc8f4ac8a9da59ace0fc575821ace88bf8"},{"version":"26.0.0-beta3","sha1":"641b3c5896d49f0080a50a0c0367432b86da96f"},{"version":"26.0.0-beta4","sha1":"849332941500fc1113371ef9450d2bd3cd51ffba"},{"version":"26.0.0-beta5","sha1":"11e4113272e5a65459fa2f898b62c5f6da88e8f8"},{"version":"26.0.0-beta6","sha1":"bfe0f3f0b2458ed8d524669948e31aece8c07d11"},{"version":"26.0.0-beta7","sha1":"a75e77eecf2a0a97fe39442d167ee5fd25b24507"},{"version":"26.0.0-rc1","sha1":"1fd29b9131de9294550577dba2da9d09e7271526"},{"version":"26.0.0-rc2","sha1":"4a9866e003e02cac278e5d65d04c7e4644bce113"},{"version":"26.0.0","sha1":"2e64aac3c6a6f201df4b7e1c0bd452d8839fb47b"},{"version":"26.0.1","sha1":"4eb1f46803873ff996f2457d1dd64c5c305818d8"},{"version":"26.1.0-alpha01","sha1":"585637e9da565fa7e211dcb701bf55d47330a30b"},{"version":"26.1.0-alpha02","sha1":"94d22464d3913f7dfc62a73740fb477a6eb1caeb"},{"version":"26.1.0-alpha03","sha1":"abfb4c1b929c3c2ae58c111b6a9c3d74f8bff0d3"},{"version":"26.1.0-alpha04","sha1":"97676ee98942f3fe2140644c54f61a98f2e984a2"},{"version":"26.1.0-alpha05","sha1":"ac7e9b5a74fc7c6a385a80d4705d5650f107418d"},{"version":"26.1.0-alpha06","sha1":"3b4ca600214ab8c2ddb21624d2ebad5b3cf7cfa4"},{"version":"26.1.0-alpha07","sha1":"5c6643bd5fbcbbfd7d7603ccabb9e6dffc5444a"},{"version":"26.1.0-alpha08","sha1":"6de31401679c132da856114c3377ff240445e294"},{"version":"26.1.0-alpha09","sha1":"c21e56a9ffd87cd85e631e16960400a5da4f54b9"},{"version":"26.1.0-beta1","sha1":"22562162a24d66a3444d30eb85352457b2db1e3e"},{"version":"26.1.0-beta2","sha1":"caf00d1da1133402c0a5c198b2871be5fd6600fd"},{"version":"26.1.0-beta3","sha1":"2245313f63912b82f25a4903e685c28056f9e327"},{"version":"26.1.0-beta4","sha1":"f604a7963780aad1a6f109ef56bf51a54a5190d9"},{"version":"26.1.0-rc01","sha1":"d37fc4261556bbdfb95898e04f37cefba6b0981d"},{"version":"26.1.0-rc02","sha1":"6e86443db13788e9e942a6730789d727a0631879"},{"version":"26.1.0-rc03","sha1":"3192009f6eead5c7a572406dbaf2a276bf79edd5"},{"version":"26.1.0","sha1":"b03b074c97afbc06d8b93336f65be0b3ac0cd07c"},{"version":"26.1.1","sha1":"7a76d33307781fffb3046301c3c375a1344a339a"},{"version":"26.1.2","sha1":"3697abf628d30042c1082ea846454dfd1e8da3e"},{"version":"26.1.3","sha1":"9d6eea0db944fe395fffed074b0161716d032afb"},{"version":"26.1.4","sha1":"dad1158a9fc38b711339960b82fd9dcf32009e46"},{"version":"26.2.0-alpha01","sha1":"71bba1ad8e38ed834d31c929969aa5c79897b1cb"},{"version":"26.2.0-alpha02","sha1":"43ac1f3e17c6b724f1e4b8b3d4732a64b53a4c7d"},{"version":"26.2.0-alpha03","sha1":"fca78a9e4544cff717188626fd0a29dec2e67642"},{"version":"26.2.0-alpha04","sha1":"f4a3e5cd8aca68642b1b67a6b4b75fbcdda4377f"},{"version":"26.2.0-alpha05","sha1":"feb7696147e22b2a47783637cb0dc9cbb7859cf4"},{"version":"26.2.0-alpha06","sha1":"fc8e9544cc18f72f2cddf80dfda3f96a9a70ce09"},{"version":"26.2.0-alpha07","sha1":"961e4b0e63b2194638d47949d0904fd9a755e62c"},{"version":"26.2.0-alpha08","sha1":"10bbc27d05f9c78095ee0a74dc4fbaf0b7b1caec"},{"version":"26.2.0-alpha09","sha1":"c0706b847611c0eb73486850133bbc85dbe5a8ec"},{"version":"26.2.0-alpha10","sha1":"d61633618915f1f58ad8b00176832dd65d5a263d"},{"version":"26.2.0-alpha11","sha1":"635e31042f2445274804c266940a0cd37d298f3a"},{"version":"26.2.0-alpha12","sha1":"44c38836de31469580964f9628fc940bb115d851"},{"version":"26.2.0-alpha13","sha1":"f4f79c675b4288eb50a35882978e025cc9edd639"},{"version":"26.2.0-alpha14","sha1":"2fdbd9a62a956552c50ee107fadc6316e93462c6"},{"version":"26.2.0-alpha15","sha1":"feb8aa87e48e921a5b6fac3317f8b2c2781667ca"},{"version":"26.2.0-alpha16","sha1":"6a4ba793421f3edbb56c09f979da431b5aa99f1a"},{"version":"26.2.0-alpha17","sha1":"1087e94cc2d0a8488216375db2260f38ae90f27b"},{"version":"26.2.0-alpha18","sha1":"fa9c0893aa2e1290fc97a0adf1b64ca8fc12c71e"},{"version":"26.2.0-beta01","sha1":"cede840066af18dd275696c88c1b2e04118d1efc"},{"version":"26.2.0-beta02","sha1":"3689862412f112e4989865c8ef5d9371b13d1954"},{"version":"26.2.0-beta03","sha1":"531f00f8e215793396b5861fa46e7858cdd60266"},{"version":"26.2.0-beta04","sha1":"588f064317fe7bd40d0e34fbe27906ddd0c15840"},{"version":"26.2.0-beta05","sha1":"a975a29cf98d1478d89af19097a3876507982461"},{"version":"26.2.0-rc01","sha1":"fc60bb34944c330dd7d2324fd405acef4dd0df39"},{"version":"26.2.0-rc02","sha1":"b477a47492c13dde9c75d34b4d17024bfa8b8716"},{"version":"26.2.0-rc03","sha1":"b206ccd4606703dd3b488ae3b947e4a1c9d1741e"},{"version":"26.2.0","sha1":"c88ff985eead6e7173b9aa8682d7b5e08794acae"},{"version":"26.2.1","sha1":"164c58ac8cf4b2bb08ad03acc5f963f4f70cfcaa"},{"version":"26.3.0-alpha01","sha1":"cbb3c03174c7d4e3df6d1375f6d5a7a28c144d51"},{"version":"26.3.0-alpha02","sha1":"2f459ce6d3ec774ed503ed114c24d75b53adb5cf"},{"version":"26.3.0-alpha03","sha1":"aa4c2218ee214bf14b19bb0f0d8ea103351ed82e"},{"version":"26.3.0-alpha04","sha1":"93b31a6fcb9521b81d7460465c3d56fef2621a87"},{"version":"26.3.0-alpha05","sha1":"727b38c066148699b8d46bf85cb32191a0f00185"},{"version":"26.3.0-alpha06","sha1":"69d7900d0a9defc6334eaf4b62d05c33ba996ddf"},{"version":"26.3.0-alpha07","sha1":"50061d1a3d6a88cabdf739d035b56434bdaf37d8"},{"version":"26.3.0-alpha08","sha1":"16124ab6f95d58769da17facffdf2d436758bf30"},{"version":"26.3.0-alpha09","sha1":"1fce6d44b16027bdd53b5fe78ad6fe7eef223c3e"},{"version":"26.3.0-alpha10","sha1":"ffad9cd6243e4a622a74346dc75111e8f39ecdeb"},{"version":"26.3.0-alpha11","sha1":"2eb2e9d8c793c8e8726f9c1113d6e69410628270"},{"version":"26.3.0-alpha12","sha1":"28b9cd78e5fe78674a7a802405a598ade48403b2"},{"version":"26.3.0-alpha13","sha1":"4c9d8a6974527e7f7f22d1c9b714b9ddba261e98"},{"version":"26.3.0-beta01","sha1":"523187b84714bfd7aff616bb41adfbd567b3edd1"},{"version":"26.3.0-beta02","sha1":"d164016cbf5697798937ff92e99f815f8408866d"},{"version":"26.3.0-beta03","sha1":"5f7c33c3cd19a125b2c977e8522a239453bd2b31"},{"version":"26.3.0-beta04","sha1":"f245aafcbe3031c73848117a320aee5d875b8465"},{"version":"26.3.0-rc01","sha1":"e54338accf226a46b3d0ef9ef7a1ad51dd247967"},{"version":"26.3.0-rc02","sha1":"3e8350d340fda7f8c8eddde71d80bd7e8df6e9d2"},{"version":"26.3.0-rc03","sha1":"adf996bba49c74d6c030797125a7196160aeafd0"},{"version":"26.3.0","sha1":"3236cec0ca345f35487c213a8570e1f673849280"},{"version":"26.3.1","sha1":"969ef3a7c0c3b245a3efe8aff7a2f6f15ff4e824"},{"version":"26.3.2","sha1":"9925a9bae35d7a0b486b504d803d91006a8e78c2"},{"version":"26.4.0-alpha01","sha1":"f23053f46f9614f347e04869f479a88a75498b51"},{"version":"26.4.0-alpha02","sha1":"c24fd3278d1517adbfd206e48d213aabf8c5c1c4"},{"version":"26.4.0-alpha03","sha1":"d163cc87ce3450fe5d2249b038ed35ccb26cfdf7"},{"version":"26.4.0-alpha04","sha1":"c3ca12d8daac54c4910c773484628d2885651c7e"},{"version":"26.4.0-alpha05","sha1":"ed9c526db21e3d38d4970c60645931344e694012"},{"version":"26.4.0-alpha06","sha1":"aa7dcd73842b6350396e93c365cf8ebf353a7237"},{"version":"26.4.0-alpha07","sha1":"b2b36c615146320fd5338616c8c5e9d7f03a6cd1"},{"version":"26.4.0-alpha08","sha1":"5db496af35e9eb00118630480ff2fe4a84ff52eb"},{"version":"26.4.0-alpha09","sha1":"5d3677a6fd62cac4267b19b904e6dd22546522a5"},{"version":"26.4.0-alpha10","sha1":"a410afbebd8b6722449455018eeb2ae2c1650977"},{"version":"26.4.0-beta01","sha1":"9ab1da164d450543d48949baed66d1ed04f789f9"},{"version":"26.4.0-beta02","sha1":"b8e9945ca1b68f5c2e458073f14d126333ea0197"},{"version":"26.4.0-beta03","sha1":"c4a55e27ce4e4768f00766c7f9e802918c016101"},{"version":"26.4.0-beta04","sha1":"784151aa8d77991c9d66e5271668d369714f14d1"},{"version":"26.4.0-beta05","sha1":"1aa53d7fee5c3a57db1a65c44d91a45a5bc1f0b0"},{"version":"26.4.0-rc01","sha1":"95401d8549fde061d32112bd20c00d31b6f86fcc"},{"version":"26.4.0-rc02","sha1":"189173ffd99e636d16dbcffd44ddee6db53607a9"},{"version":"26.4.0-rc03","sha1":"7c1684b074254837413a144847c3ff14351d34fb"},{"version":"26.4.0","sha1":"c76e05e0c19ae0f5fffe2dcc190bd5a0aa7b546b"},{"version":"26.4.1","sha1":"d8c42109324425b0997efa35ed842f84a435908"},{"version":"26.4.2","sha1":"d3c02d00d2a34adebd9cfce506414076419ec06c"},{"version":"26.5.0-alpha01","sha1":"7232e9488c696a4423a08f5a34f5b74074cd78ff"},{"version":"26.5.0-alpha02","sha1":"a4692fce434536a745cc0cb631fb2c971f4fb945"},{"version":"26.5.0-alpha03","sha1":"73f01fdc42736e38a7f13ffb2f244c1289e51766"},{"version":"26.5.0-alpha04","sha1":"92e4b927aefcbb0ea51c550987ca746b611b3ef7"},{"version":"26.5.0-alpha05","sha1":"9edc3e5c1a35b23fc402b19fe4d2bce17476e10d"},{"version":"26.5.0-alpha06","sha1":"4ca05ad38a5806a641c4125d5fe6e463fa726899"},{"version":"26.5.0-alpha07","sha1":"12a5e9a00484d7d8a3f51ab6d2369a26396a7b37"},{"version":"26.5.0-alpha08","sha1":"94e558448afb2bcff27ce40f974f997189c45938"},{"version":"26.5.0-alpha09","sha1":"720f94dcab0c3fd6ecfe23e3d1cd65419513b4b4"},{"version":"26.5.0-alpha10","sha1":"488829fbdd2887aaf64f0ac90ec4bea937204adf"},{"version":"26.5.0-alpha11","sha1":"31b7ca60b4c0c48152a7b3fe013dd9c01dc11e0"},{"version":"26.5.0-alpha12","sha1":"cee3fea1787e47a9557acf4ffb27e757167d7af3"},{"version":"26.5.0-alpha13","sha1":"1a5a3a236798e16f242e44d5607e1d20e2c0e082"},{"version":"26.5.0-beta01","sha1":"bd569f76323d1a7ca077581c753745e25c787ad2"},{"version":"26.5.0-beta02","sha1":"c8fa1264adf39002d52328012b7f636ecf6146a1"},{"version":"26.5.0-beta03","sha1":"98742c6d5a1c90527454268df583c9df3747385b"},{"version":"26.5.0-beta04","sha1":"abf5be857773b1ae7010e2be6ac7c8c9018ec74a"},{"version":"26.5.0-beta05","sha1":"3046d8d690a6c55ab21d4f433858a2953eaadbd6"},{"version":"26.6.0-alpha01","sha1":"55371db2ac6fb439e5e7cc3ba5df010de3242651"},{"version":"26.6.0-alpha02","sha1":"753b3ee6ceb0df5776e06f34047129f818fd35a1"},{"version":"26.6.0-alpha03","sha1":"4281a3d0aed11e90e7bffb53d089d667def16ac1"},{"version":"26.6.0-alpha04","sha1":"d563e4bab63201b582b4f661bbdd4f6a590d4644"}]}]},{"group":"com.android.tools.ddms","update_time":-1,"packages":[{"package":"ddmlib","versions":[{"version":"26.0.0-alpha1","sha1":"bf5a01ffb5e1590a86d2e8bded5ebdc1834d3c38"},{"version":"26.0.0-alpha2","sha1":"8b73666ab4fcb66291fe7a27799bfa26755bfbb5"},{"version":"26.0.0-alpha3","sha1":"2525782587b3dd348eac6d4f9787427de227d4b2"},{"version":"26.0.0-alpha4","sha1":"dd9a0fbf5eeba6e6080cd360ed119e6bf7e0b"},{"version":"26.0.0-alpha5","sha1":"cc8c3ceb41cdbb87b395dc4c64becc44111a237b"},{"version":"26.0.0-alpha6","sha1":"a20c37f3b3e1b2bac650d1c91f538bbc006ff8c6"},{"version":"26.0.0-alpha7","sha1":"79d3fd143973a15f9721ea2e0bbf0b88d17d7611"},{"version":"26.0.0-alpha8","sha1":"b74088ab02948758eda9376c1ee2452a2c2bad44"},{"version":"26.0.0-alpha9","sha1":"3fc3e83dc4bf9f47a58014360b1598ae3030e390"},{"version":"26.0.0-beta1","sha1":"3827382925b05fc87f2e672b311d66f2546bbe2e"},{"version":"26.0.0-beta2","sha1":"fabc3f5d892f41fa502f2e58da9c63f1281685f7"},{"version":"26.0.0-beta3","sha1":"79a9f030b2183c3f346d4677c286f0e17024a5df"},{"version":"26.0.0-beta4","sha1":"93799eb302c41dbe845e87930182bba74baf6a18"},{"version":"26.0.0-beta5","sha1":"1a2db89381fdef27c499061bb4032beba858f4ba"},{"version":"26.0.0-beta6","sha1":"bdd8240e7fe20bf60b02ae74dd75645119d88ac5"},{"version":"26.0.0-beta7","sha1":"acbef1ceadebc4ccf0048c85c688a992a51ac14d"},{"version":"26.0.0-rc1","sha1":"68df8b67e99bc799d89ec8327e4783f3d0fd1eb9"},{"version":"26.0.0-rc2","sha1":"5274253089cd3d04df4e7704c3954d349c4f6e5c"},{"version":"26.0.0","sha1":"79daeff3eab3fc4ff963f5717392f1f2476ed788"},{"version":"26.0.1","sha1":"d4bcdb3a578812688ccc34f1359702601fea955d"},{"version":"26.1.0-alpha01","sha1":"d61d20732822f89fb34edf62d30952fc24ca346d"},{"version":"26.1.0-alpha02","sha1":"7edf0e2809829143d9a8b99b1614b21c220182cd"},{"version":"26.1.0-alpha03","sha1":"e56a38f9a0e5d8deb8588c6f837a3bba406c079c"},{"version":"26.1.0-alpha04","sha1":"172ddb01fe46bce9af22abb0b4263b25c41cf5bb"},{"version":"26.1.0-alpha05","sha1":"f6c17995f9338f8bea3f87dbf4b0aaf963d2f878"},{"version":"26.1.0-alpha06","sha1":"c83713e5291eb8b034279be3ffee56cb0c812dff"},{"version":"26.1.0-alpha07","sha1":"c8159a5c784ed26b6b0be7c958cc47ee51be7a86"},{"version":"26.1.0-alpha08","sha1":"2b778cb0c6768a72ba2a9412df71efa5140b7a4b"},{"version":"26.1.0-alpha09","sha1":"82cb6527606b7bcdea7a04c31924dc70749292ba"},{"version":"26.1.0-beta1","sha1":"355d00e2e18b548065b9781d71234605f8863fae"},{"version":"26.1.0-beta2","sha1":"2a4dfddbcd7b5614e9f915f6157a8b5fd30cfa46"},{"version":"26.1.0-beta3","sha1":"733100bbb12ec8eb70189abcefdf45ba52726d91"},{"version":"26.1.0-beta4","sha1":"448a6b2ec304e0e9747bd279f3f97d06add37112"},{"version":"26.1.0-rc01","sha1":"a4cf3313507ffa0c479b80bcc7dcd4aefed93c5c"},{"version":"26.1.0-rc02","sha1":"8cafb2bd2210c00abe40d9dc3760d1b97d2022d3"},{"version":"26.1.0-rc03","sha1":"d2e1feb0f1bf03041b55619d2738a905c38b2795"},{"version":"26.1.0","sha1":"998c179991f12a3c13d8e0122caf7b8b9f1406a4"},{"version":"26.1.1","sha1":"38e701b19a843e05de66e355a850cbbf001dfc6a"},{"version":"26.1.2","sha1":"1d423e621fb5c89fed13e41d0ed026cf5d8d7e7b"},{"version":"26.1.3","sha1":"753d87a48d41e2ce6d6ba57962a18c5f61d368d4"},{"version":"26.1.4","sha1":"c11fa14b583412aaa9262461299318207439156d"},{"version":"26.2.0-alpha01","sha1":"c48418aa975178ed95c54a03b42692fcbf3c180e"},{"version":"26.2.0-alpha02","sha1":"155315cae3a11498635a37171e19d062f2928b94"},{"version":"26.2.0-alpha03","sha1":"cb4878de6d3c0d6a23dc064688539c9b15996c40"},{"version":"26.2.0-alpha04","sha1":"893b2ed35f0fe4e890920acfcd4c1e80a6be3e27"},{"version":"26.2.0-alpha05","sha1":"47c24052494c146ce4d1db99053f405f33df13fa"},{"version":"26.2.0-alpha06","sha1":"a1e9c39de987495c19bc16d0b610cbb7f5692048"},{"version":"26.2.0-alpha07","sha1":"11f6bcd50f7d903fb9fe6dcfd1ac2db8448b35e4"},{"version":"26.2.0-alpha08","sha1":"8ef7a1746b4faf94969c129d84e58552bbd5f7d4"},{"version":"26.2.0-alpha09","sha1":"d7bed8a4ed991dd7a731e49850c39365eca0cde0"},{"version":"26.2.0-alpha10","sha1":"4fa5b16e4f79504e7f14eb43a553cba75fe1eb1d"},{"version":"26.2.0-alpha11","sha1":"4d828c63d34f7ca56d43ebba39ada5b52ba19900"},{"version":"26.2.0-alpha12","sha1":"d5dcf36c392356c63c146796650293abcc337e05"},{"version":"26.2.0-alpha13","sha1":"39165f834693d2979dff8f640170f611c561f4f3"},{"version":"26.2.0-alpha14","sha1":"8d0f928e6a0e4ecde399e700e5172e45ad55ef1f"},{"version":"26.2.0-alpha15","sha1":"b20af10660293d5b96e69a8432f18b37dfe9fbde"},{"version":"26.2.0-alpha16","sha1":"555031173774973bbd5023694adcb9c14fd4257a"},{"version":"26.2.0-alpha17","sha1":"5e7db42f744bbc8af8fa1ebf33dbf47f67e42d14"},{"version":"26.2.0-alpha18","sha1":"b106ee6bff7c0923cabc0bb46890f2e38f4ae414"},{"version":"26.2.0-beta01","sha1":"384aa2651f9b77f12ef0e14b3937b33e2502549"},{"version":"26.2.0-beta02","sha1":"2e3cb63ce50770a18e34ad969792ecfcd4060dd9"},{"version":"26.2.0-beta03","sha1":"216b76ccb5ea52ea2ae9bde499b6341eb7c31d5a"},{"version":"26.2.0-beta04","sha1":"85d020aaba0f5bc103efc80e98f335fd135c2201"},{"version":"26.2.0-beta05","sha1":"9695cae44758884654dc88bad0fe260115d8df14"},{"version":"26.2.0-rc01","sha1":"1096469ae758a5d76558a135fbe20b7cc7b7da99"},{"version":"26.2.0-rc02","sha1":"4d4bccb37444b7972dcfe7ac1cce730c8c34c9cf"},{"version":"26.2.0-rc03","sha1":"190c1684eba798336cae51b7ac56d0506a7c95c2"},{"version":"26.2.0","sha1":"3568f35614039495ca9b4bf27142a8d879f616b1"},{"version":"26.2.1","sha1":"88fbd27836d660bbbc3823bda8da3085cef41f6"},{"version":"26.3.0-alpha01","sha1":"979034d3706ffb1996fc1b7f63ffca54fee62409"},{"version":"26.3.0-alpha02","sha1":"9c5b99f2ebe9fbe740482e2ad4c9b07c615dd6f5"},{"version":"26.3.0-alpha03","sha1":"c07272f6ed190ce7c358365042fd2acc8285ae3d"},{"version":"26.3.0-alpha04","sha1":"10497164a42f527d58095f1598dd9e6fb8a6eff5"},{"version":"26.3.0-alpha05","sha1":"4ee8ac5070850ec7672f4ad5f59de475a853d7af"},{"version":"26.3.0-alpha06","sha1":"61fddceecfa1000c23e718eb5c5ebd4145af9da"},{"version":"26.3.0-alpha07","sha1":"eeeb21e522888b70b41448d110e15bcc9eba8cf3"},{"version":"26.3.0-alpha08","sha1":"a3dd323c9cbf48762d614ce881e9bcbfe904df12"},{"version":"26.3.0-alpha09","sha1":"f777822a066ec1ead61dc57d77e31dbb352c229d"},{"version":"26.3.0-alpha10","sha1":"52b1ff67c10992f9c183a9c1aba1549324444c4e"},{"version":"26.3.0-alpha11","sha1":"ed7b6b83e8800cd9a4e7c085beac5fc9bcdfc075"},{"version":"26.3.0-alpha12","sha1":"30bb31ccbf2b33027b7883e632184be182292ea6"},{"version":"26.3.0-alpha13","sha1":"c474327e44b05f97034a1117a8378e13ede115de"},{"version":"26.3.0-beta01","sha1":"8243a8c70f03d4b33c49f4843723f9c8503699f0"},{"version":"26.3.0-beta02","sha1":"fa2418c5a257246d6e64488c5fd9fff88bbc0080"},{"version":"26.3.0-beta03","sha1":"992f7174f951e66381071a30642ada062e2df4f3"},{"version":"26.3.0-beta04","sha1":"290d0e399402020a1e6474e0daef3f334ee090d"},{"version":"26.3.0-rc01","sha1":"7a98ba52acd7d29bdb4dd86711082fbce6a2d97c"},{"version":"26.3.0-rc02","sha1":"66e6d8d87c44e0edd8288c324292806d6a688526"},{"version":"26.3.0-rc03","sha1":"3161e6e33bb23d06b074e95b082104f90f9bf6ad"},{"version":"26.3.0","sha1":"11ab05455d247934cc6c66201da7b3d4ce0f6b11"},{"version":"26.3.1","sha1":"61ca2873fcce1012ba9782adf7e9b0e164fb6007"},{"version":"26.3.2","sha1":"f8f5ae75e8242312c59e2167d81117200a7b6129"},{"version":"26.4.0-alpha01","sha1":"d5fbe9d51cb1e2a8d41478759323dc1555d8e859"},{"version":"26.4.0-alpha02","sha1":"1b2a7984e9d53ceb0d69bae965c45dfed5becf8f"},{"version":"26.4.0-alpha03","sha1":"23a89eb7fff635a7325eacf540a37a6c826ccd5f"},{"version":"26.4.0-alpha04","sha1":"b82b5cde4d23afb43bf41a9cfe3a76fde37840c6"},{"version":"26.4.0-alpha05","sha1":"9aaf2d1a0ced658b244bd7036ad96b6aebbf608d"},{"version":"26.4.0-alpha06","sha1":"69d362e8324bff6309dfbb7937616cdc9ec0c88d"},{"version":"26.4.0-alpha07","sha1":"5e5e580c2d0e961359e595dd620fe062b11b76e9"},{"version":"26.4.0-alpha08","sha1":"92a97203889a762f08c8b3897cf6c692c41d644"},{"version":"26.4.0-alpha09","sha1":"2a6b835ed4b90a2305ae54ae67edf007fd45f3c6"},{"version":"26.4.0-alpha10","sha1":"c518d4890b6977684a8127113c425345b4625d27"},{"version":"26.4.0-beta01","sha1":"ba94d600594686a6be2a888a6401e30eeac44a66"},{"version":"26.4.0-beta02","sha1":"71581e090f0988aa46b42c4b3e57701723b4c307"},{"version":"26.4.0-beta03","sha1":"a333c1d1b4cf804d1fc990efe76563c0224805ee"},{"version":"26.4.0-beta04","sha1":"79d2e878fb3b51e37ec7d1ca728d6dda3685df8f"},{"version":"26.4.0-beta05","sha1":"3a4d2f416509486252480bd56fc2f43740302681"},{"version":"26.4.0-rc01","sha1":"8f3e6c5a41155232d4daa59c0935ccc017566831"},{"version":"26.4.0-rc02","sha1":"9b057fed96de92ee2c6d732fc0591d40f8e59f8f"},{"version":"26.4.0-rc03","sha1":"41dfec661a6a657306e0721f1bd0f8e38cb268f6"},{"version":"26.4.0","sha1":"889c2419aefceeb31749d49615eb64e798747458"},{"version":"26.4.1","sha1":"3da8e5681c69f932e598a9fed1ca8508e069481a"},{"version":"26.4.2","sha1":"d6f31515eab2a935c3f2745094f0e8114d4cdf47"},{"version":"26.5.0-alpha01","sha1":"eeb56bc656d69bc560eb439acf6261ae4156f63a"},{"version":"26.5.0-alpha02","sha1":"26e0aa4f9be4da0ef7bb2dea9f66ac996c2733c6"},{"version":"26.5.0-alpha03","sha1":"17eff4e8498de1a1f6d9de3f2ed220681d258480"},{"version":"26.5.0-alpha04","sha1":"1e4710a782ae33a697de8d7e193483ae0996bbd9"},{"version":"26.5.0-alpha05","sha1":"9fde077d727df1a817fbeb39d9a3e912a0a8e68a"},{"version":"26.5.0-alpha06","sha1":"ac956bc45ff510ab6201e99b516c500dfcf7b39b"},{"version":"26.5.0-alpha07","sha1":"c82dc3c7ef3d3fe245ae38c34f7d8d6d096e5130"},{"version":"26.5.0-alpha08","sha1":"27ff8ca50dfece69b8fd8f0c53f0eff5d73f4011"},{"version":"26.5.0-alpha09","sha1":"12454eb3bc032618fdb8b3cb2704e51528c293b2"},{"version":"26.5.0-alpha10","sha1":"fd68b5d59284de9f541dfe4a68d88e2453afe6a4"},{"version":"26.5.0-alpha11","sha1":"64936059019657cbdb5891b3e0648db4cf5f6cb5"},{"version":"26.5.0-alpha12","sha1":"ae38e54e6ad83317fec91ee4aa235731ed2fbc46"},{"version":"26.5.0-alpha13","sha1":"d3e08a6c80f433e10250e5a0d451b246ab27373"},{"version":"26.5.0-beta01","sha1":"ed6dc9c75c478a5a8555d028db9aafa576dc72ea"},{"version":"26.5.0-beta02","sha1":"74219fe9ce3aeb8050a16c4f0b58776d60f92713"},{"version":"26.5.0-beta03","sha1":"7cbc1c902a650a41cee26438a00fe75c6b062fcb"},{"version":"26.5.0-beta04","sha1":"1e7139d63301162938c60ff071ad5c4193e4c484"},{"version":"26.5.0-beta05","sha1":"5419994a216ec12d126078863532445cd913688d"},{"version":"26.6.0-alpha01","sha1":"d0c90727f56369bd021b2bf95cf22d31e47c07fc"},{"version":"26.6.0-alpha02","sha1":"65b360e4ed2d160ee9faf90c2425a9e578b4f8bb"},{"version":"26.6.0-alpha03","sha1":"78788f4c0e4f3f8a87ce884c6209d63a65d0c045"},{"version":"26.6.0-alpha04","sha1":"d80f319b9d44e47c7f8d02504a0ce3af493840e6"}]}]},{"group":"com.android.tools.external.com-intellij","update_time":-1,"packages":[{"package":"uast","versions":[{"version":"171.4249.33","sha1":"baec72d79d62dca54c22b4941f95b157d32fe59a"}]},{"package":"intellij-core","versions":[{"version":"26.0.0-alpha4","sha1":"a1a08542c1d90d6e2eb4a835fe334e8b56522a34"},{"version":"26.0.0-alpha5","sha1":"7c296fb99074292f73660a24a60868c2e21f59ee"},{"version":"26.0.0-alpha6","sha1":"e812f8dcd21e7db4528ecc42a11b1464abb08c2a"},{"version":"26.0.0-alpha7","sha1":"583294543dede6c858393f069df11f7135b31d92"},{"version":"26.0.0-alpha8","sha1":"85a8c13f038a738581cae13e0b441f47237fb555"},{"version":"26.0.0-alpha9","sha1":"240c50614e88f5361998db44e15af79089ad9f26"},{"version":"26.0.0-beta1","sha1":"6c57a769736bbf3cb5748afb594cc81ff9867389"},{"version":"26.0.0-beta2","sha1":"3c9c0b03351e7d6e92a3de99c6c10beebeef7c21"},{"version":"26.0.0-beta3","sha1":"707068e053263b3cbdde12f643956b781265e6b6"},{"version":"26.0.0-beta4","sha1":"ad2ca1b37507cd5297c576f4097d301f030f3d2f"},{"version":"26.0.0-beta5","sha1":"8783fecf2961bacf5d552392d0b3204518e54d50"},{"version":"26.0.0-beta6","sha1":"cdee7dc8222a3a541161b48ec97f2cf62e3b3a2d"},{"version":"26.0.0-beta7","sha1":"ef59f3223c3c3ccf7858e25a7fde63ee9fcab0c1"},{"version":"26.0.0-rc1","sha1":"35b3cd832bd59d1ce06182ffdce200565bbcf367"},{"version":"26.0.0-rc2","sha1":"50000d2b8260cb03f4997fb7e6046b083b83aad"},{"version":"26.0.0","sha1":"47396aa1fead8512c246fdf4698552c1f33b1710"},{"version":"26.0.1","sha1":"a373ed549821cb9a932b87c79792790f6d018727"},{"version":"26.1.0-alpha01","sha1":"5a2ae7bd5a688bb52e7569f9e43897fcb8e5aaba"},{"version":"26.1.0-alpha02","sha1":"749ef70f739fe0c9b4c84482b9b136e1b2daaae5"},{"version":"26.1.0-alpha03","sha1":"b0a81b5a87af581cf45668682459b3cc0ca6a1de"},{"version":"26.1.0-alpha04","sha1":"d135a16d5e278fb426b34f95625322a911d73941"},{"version":"26.1.0-alpha05","sha1":"5a03a695fd544ab5c84c453d45b8a966e7f626b1"},{"version":"26.1.0-alpha06","sha1":"81314696e859b721e14ace112076b2eea3d82bbe"},{"version":"26.1.0-alpha07","sha1":"31e2d37796a241b382f224c3bd79795db108fd46"},{"version":"26.1.0-alpha08","sha1":"fb28b8ec0b08e10ac376a9d884a5cb9351ff00ae"},{"version":"26.1.0-alpha09","sha1":"f85bd74d989d734a6c6e989ead5e32cb409421f3"},{"version":"26.1.0-beta1","sha1":"293cab80786c01356438f2a64e9e33dc689ce4a2"},{"version":"26.1.0-beta2","sha1":"a26d09d6535419f47f7b14a6a5b4e2c560fa6a83"},{"version":"26.1.0-beta3","sha1":"a56d2aca10b3fed2c8d26b296e01bbdfeb65cd26"},{"version":"26.1.0-beta4","sha1":"8d02216bb19d39a51f241f0bfcf12c3ff656f7db"},{"version":"26.1.0-rc01","sha1":"724b02d884ac7291a0ef8f68194074ffcebdc45"},{"version":"26.1.0-rc02","sha1":"1461dfe1ca29fa5ca20cdd70b3283b08862d9690"},{"version":"26.1.0-rc03","sha1":"50b7bfd346d5d9dfacd9569bd679ef6cf022b51e"},{"version":"26.1.0","sha1":"6f24dd54ae33b29411ce24134673a6c810a56027"},{"version":"26.1.1","sha1":"3d033a484de1eec44024c61279f40709d353ab82"},{"version":"26.1.2","sha1":"6d3d4fe794977afea8311122a64629bd42397d5e"},{"version":"26.1.3","sha1":"ad773b3f3056c8927c97d70ca1c291faa73b3a7f"},{"version":"26.1.4","sha1":"93773bec23b712d959e0f826166f845d579d46e3"},{"version":"26.2.0-alpha01","sha1":"5dcf6371a107f66336c3e8a9d9d49e0ece1086fb"},{"version":"26.2.0-alpha02","sha1":"8c72c30ad7b24f07be614aae9be2cd269a1383e7"},{"version":"26.2.0-alpha03","sha1":"37311bf90b6434536c8b2b33672ea41847aa9efb"},{"version":"26.2.0-alpha04","sha1":"c902a7e2bb9e01bd4238c60576c8c4fc556b6d0a"},{"version":"26.2.0-alpha05","sha1":"a253c9ab676984b12a2c5facb0d37073271678f2"},{"version":"26.2.0-alpha06","sha1":"4f6a8f0f9b867a2de0a55c2ddce8fac49fac214a"},{"version":"26.2.0-alpha07","sha1":"ebe8ae29095bf2e0bb858a78b3eafd922eb3fde1"},{"version":"26.2.0-alpha08","sha1":"df3f6ea56211b572bfc4b603a38e6c2a66cc2de0"},{"version":"26.2.0-alpha09","sha1":"1078c50a9b63039c007a580aab6fb82a9da15651"},{"version":"26.2.0-alpha10","sha1":"dfd1ebdda9e7b45e9f9a4c938eb0bbb0214a1c8c"},{"version":"26.2.0-alpha11","sha1":"d830712236946990f3e7a59beb94c05f6ad337f4"},{"version":"26.2.0-alpha12","sha1":"283fa29f877c0fd9c9d30137886a4160390a835c"},{"version":"26.2.0-alpha13","sha1":"b63908494f46ce816e8cb1f965cc41bd50ed07b8"},{"version":"26.2.0-alpha14","sha1":"36d2bc87c1c3843f13ee110d4a3469b8059c2e33"},{"version":"26.2.0-alpha15","sha1":"ea3462c24c451c980127f0fc7dcc388c21db75a2"},{"version":"26.2.0-alpha16","sha1":"cfe643e6d65d295344740cf769ef607694e73139"},{"version":"26.2.0-alpha17","sha1":"627be1c7bd27a7d5e6095919415e0788df36ba64"},{"version":"26.2.0-alpha18","sha1":"11009ba2266482d34ee031ebcc7265f28a172db6"},{"version":"26.2.0-beta01","sha1":"cfde50e17bd4d6bc0c6b8912d692b985c55d46ad"},{"version":"26.2.0-beta02","sha1":"906257838de83440b628b761e4b1dab610f16c26"},{"version":"26.2.0-beta03","sha1":"cfdb5b680494849e3bb3e0aea2a40f34cbcd8aba"},{"version":"26.2.0-beta04","sha1":"298003cf76a4cc599c8a3391d8604d920b411188"},{"version":"26.2.0-beta05","sha1":"815543351ee1a00b79868c4c83f15aef9beb803d"},{"version":"26.2.0-rc01","sha1":"c20f3168297abe266d5a9fb5b82068fa032555d0"},{"version":"26.2.0-rc02","sha1":"2eb8fbd003d28dbfe0518568f29683aae4f5a111"},{"version":"26.2.0-rc03","sha1":"66b55b99ea1177d4271b7a75f67d9c0d524ca559"},{"version":"26.2.0","sha1":"21e62ff6be22ced59b36a329bde3342185827389"},{"version":"26.2.1","sha1":"284e294ec4e98f820acc5d32caf6b69d69398363"},{"version":"26.3.0-alpha01","sha1":"5d78b71143b0c4edd283438df6a178ea6e153503"},{"version":"26.3.0-alpha02","sha1":"baed2cad161ea99860d6cb43774564524d191fe0"},{"version":"26.3.0-alpha03","sha1":"fe8eb788858ae9ea7516e6c730a677dd81474d04"},{"version":"26.3.0-alpha04","sha1":"b30f0c98580689e4872623a764f041a9f3e25caf"},{"version":"26.3.0-alpha05","sha1":"c5b2965a7420e5a7d991d4f7a8cde361b0689439"},{"version":"26.3.0-alpha06","sha1":"cc6fd283ce9363ab675a8602eb99db53dc8b0118"},{"version":"26.3.0-alpha07","sha1":"7b901fea1efd0e9c63f3e5589713d887ac019b66"},{"version":"26.3.0-alpha08","sha1":"6ed68f7c4782fed7d5d0d542cb52805faaeadc1d"},{"version":"26.3.0-alpha09","sha1":"e586ff0f1f4ba8da9aa273ae4a5ddb2fde8bedad"},{"version":"26.3.0-alpha10","sha1":"85b44b4947b82bd6a78cd58af749eb9ec6555c40"},{"version":"26.3.0-alpha11","sha1":"33ae3ac6900bab61a2b64cfe92813c69a63bd0c7"},{"version":"26.3.0-alpha12","sha1":"9e91abfd912924770aeeeedcd4e638bd15941a72"},{"version":"26.3.0-alpha13","sha1":"8da7785f095628f1124159fa09f398e77c200595"},{"version":"26.3.0-beta01","sha1":"c464068ef564aad69bf93e9ea53f1f571f76066c"},{"version":"26.3.0-beta02","sha1":"117a8cb9c6160c8aa97b2ec2f9cfdf2adc19b5c4"},{"version":"26.3.0-beta03","sha1":"eba5822b51590a3f41edf34f03f9260da63b235d"},{"version":"26.3.0-beta04","sha1":"1bc7022ac0059941bf23b67334990d0fd1a4d2e"},{"version":"26.3.0-rc01","sha1":"a46c180aa68037825df35da2e20d750022b8d052"},{"version":"26.3.0-rc02","sha1":"b6c97f7a128bdcb00a61f7ff1a2e420dfda2b3cc"},{"version":"26.3.0-rc03","sha1":"a578da0cae8f7355de3281fac6dbd9a680a10f4f"},{"version":"26.3.0","sha1":"19546f266e5f18581d37375583031e9831c584e6"},{"version":"26.3.1","sha1":"73ba0f88dc5c5cdcc9839a4c3bfa50cdd6899bb8"},{"version":"26.3.2","sha1":"1cc3c8681a465cbc8dcc89bc3f73eafede576c08"},{"version":"26.4.0-alpha01","sha1":"817f54423d9e065b84a7d17766470d44e4a65c41"},{"version":"26.4.0-alpha02","sha1":"54f42211f1e6ab7122dd60770a73ee1366db1b62"},{"version":"26.4.0-alpha03","sha1":"47ef15c2f2d66ac9f708ad668d63f25e54c863f6"},{"version":"26.4.0-alpha04","sha1":"c040adf874257c4ecfe69195513a5b4f5f4c1a14"},{"version":"26.4.0-alpha05","sha1":"c0b985cd1169360feef0f6ee730856c65efcf921"},{"version":"26.4.0-alpha06","sha1":"bc25c1873c3da1585b9dfa436ce22a053d86b202"},{"version":"26.4.0-alpha07","sha1":"2f80ef5277f77322f5630d735c052a7f7302f732"},{"version":"26.4.0-alpha08","sha1":"7b3630c9238ccb577cccfeabab4add1c8f2f0ce5"},{"version":"26.4.0-alpha09","sha1":"6547de382ba72c17644bb660c32ee91f3c5a6612"},{"version":"26.4.0-alpha10","sha1":"f9c796ad4a12090579bdd7edb6c06663a6f547d"},{"version":"26.4.0-beta01","sha1":"b24f9f46d6e51a071f29c08d8d0afbbf3f6bc7a1"},{"version":"26.4.0-beta02","sha1":"4a1823de31649681b5bf32dfbd56ca5d7a1d0f66"},{"version":"26.4.0-beta03","sha1":"9d018b4f726b9c1471f14dd70cb80fc55e70a408"},{"version":"26.4.0-beta04","sha1":"11b9752043538d84683642eadce44c2df9723d74"},{"version":"26.4.0-beta05","sha1":"61a4e7b0c73823dca61fdbdf543ab2da06b86a2"},{"version":"26.4.0-rc01","sha1":"7640f7f6d89a7ff6b617576a7e6953d148865a15"},{"version":"26.4.0-rc02","sha1":"4f0c25cc54de0cb2039f8272e5ea5d9ca75b0f8d"},{"version":"26.4.0-rc03","sha1":"a24ddae5615fe0a04a01172c8d29d95dae7d463a"},{"version":"26.4.0","sha1":"3d9d4e89afa60528bbc4fac51e25326508acda54"},{"version":"26.4.1","sha1":"2493fbe109cfa337e50db8ced41fac767e1bc40d"},{"version":"26.4.2","sha1":"e848324c1801623cf2bbeb5e6841413d30d87ced"},{"version":"26.5.0-alpha01","sha1":"274b5286a9b0f6f78410795d7f043fd29c5bf1de"},{"version":"26.5.0-alpha02","sha1":"e7ffe1dae3ce7710517904af92e2dc2b495103ae"},{"version":"26.5.0-alpha03","sha1":"12a45e37bbd5a702213a18ea867b9031986f1c0e"},{"version":"26.5.0-alpha04","sha1":"9c3bc47320d3b5de0bd475610c1fbf281babc7ff"},{"version":"26.5.0-alpha05","sha1":"26e8530cc531ad1b896a3a3561afc583f017350a"},{"version":"26.5.0-alpha06","sha1":"788a1a49f236318177d4ebe6e234b11075fda43f"},{"version":"26.5.0-alpha07","sha1":"bd49e374944547bee0b59554f97f5ba83a7fcf54"},{"version":"26.5.0-alpha08","sha1":"8de83f5425468694feedf2de406ca0e893a896bd"},{"version":"26.5.0-alpha09","sha1":"b9e0304a25ba9870964570fa9e8f2781085c7678"},{"version":"26.5.0-alpha10","sha1":"a72bf00ff754aa6ca522f8b4b5ce252375b1e3c5"},{"version":"26.5.0-alpha11","sha1":"3dfe7541cf534b1d51d1bfcd3471776efd53b711"},{"version":"26.5.0-alpha12","sha1":"5afeebd25c30c4e19cf5902e58aec5a07385f6c8"},{"version":"26.5.0-alpha13","sha1":"48b76336fd13fa41224771235bc0a1c639050035"},{"version":"26.5.0-beta01","sha1":"2d0c1494318af6c7f3657e8eaddb6247dc768194"},{"version":"26.5.0-beta02","sha1":"297bd96a7c770d00b9662c41394d689314f2e0c4"},{"version":"26.5.0-beta03","sha1":"7f00459eb6cc3d12231035bbcaad310195cac25e"},{"version":"26.5.0-beta04","sha1":"95d1fae465b8f9b9d7ae3a8c0788619333e72bbf"},{"version":"26.5.0-beta05","sha1":"74a4648fcf70f755dbdc01b2a70736dad5847c74"},{"version":"26.6.0-alpha01","sha1":"16b8bf20f59ce15ea668ce442d6a7e2e48c9e94a"},{"version":"26.6.0-alpha02","sha1":"f943b175d9330c9c33c2795703f4ac7c5048ff29"},{"version":"26.6.0-alpha03","sha1":"446ffc21dc4b9f05d29123f11fae5c3f270a3529"},{"version":"26.6.0-alpha04","sha1":"d60066c73de8938f050b43fb25b1a4227100dc40"}]},{"package":"kotlin-compiler","versions":[{"version":"26.1.0-alpha01","sha1":"101ee05d3180ca65ed1aa8e2cc1c5f6a9e59b71b"},{"version":"26.1.0-alpha02","sha1":"6cf35684dad49f2e2382f08a91ebd2cad9510b1"},{"version":"26.1.0-alpha03","sha1":"6023f2d418b7ab767d65bbb3d390252978ca00da"},{"version":"26.1.0-alpha04","sha1":"4f3e75f82ebe88672aa6f1eefdf5a6bc92cf5ecc"},{"version":"26.1.0-alpha05","sha1":"befe8242fd640ff811520ccf298adac74246e029"},{"version":"26.1.0-alpha06","sha1":"f0ec3439d3a691816628b91f7e818dabf57a1664"},{"version":"26.1.0-alpha07","sha1":"297afab3cfaa7f25832130ad007beb910d4b0c4f"},{"version":"26.1.0-alpha08","sha1":"864005a8f5a9873de270b36bcabda252167a7984"},{"version":"26.1.0-alpha09","sha1":"28c43592cf78ba7a866350b337fdde00f7489d0"},{"version":"26.1.0-beta1","sha1":"dea0564651cc17394a9feb73d0cd406f2572887c"},{"version":"26.1.0-beta2","sha1":"64184dab3a65dbbc8b4cace49053ed74139411a0"},{"version":"26.1.0-beta3","sha1":"c84e1394208919c186d9ac77c0bd566b7e020dce"},{"version":"26.1.0-beta4","sha1":"bfc58f39c545f547b0b5a21f26ab335fb59e8cd8"},{"version":"26.1.0-rc01","sha1":"cd1d2f7cfa31411a2d317e10ac2cb410a8634d9f"},{"version":"26.1.0-rc02","sha1":"749f208f06cca6fe5f07ad5f6241a0a56107ae06"},{"version":"26.1.0-rc03","sha1":"f3e93ee1d5d999a85881400b400b1e647276e30c"},{"version":"26.1.0","sha1":"f05c0d5411ccda5bb4242d5cdc9af4b5e991cf0"},{"version":"26.1.1","sha1":"5660b953b84420985c5e72ae58a8f9f0169d8fc5"},{"version":"26.1.2","sha1":"73f40f973a86bec87921605b9ac322b71ac98dc4"},{"version":"26.1.3","sha1":"5cd32786f7a0e0c3f68a13eb0ea7dcfd2233931e"},{"version":"26.1.4","sha1":"274438d4dcde218d1a9f096929e5bcb033c73df1"},{"version":"26.2.0-alpha01","sha1":"e7d9995baddc534f03f1fed216a61df9492cacc0"},{"version":"26.2.0-alpha02","sha1":"44743bd15c091bb8f9f97ff918585f40ff8e4bc7"},{"version":"26.2.0-alpha03","sha1":"517b4123e3a691293719d6717ee56720ce3a5457"},{"version":"26.2.0-alpha04","sha1":"774a22dc8c70ad164bfbac2bd71e48ca5a815fe"},{"version":"26.2.0-alpha05","sha1":"dafe168da4b916a69c6e726265b25850efeb9ae3"},{"version":"26.2.0-alpha06","sha1":"b4a1634fd11a428f2d27270274f5a812caa4bce2"},{"version":"26.2.0-alpha07","sha1":"3dd2e03dbf828c4ad4a7b420d289556335e47d49"},{"version":"26.2.0-alpha08","sha1":"4927aa2d6d5f49fd62e840cd94729762458ce9a6"},{"version":"26.2.0-alpha09","sha1":"c497b1fd704d5caa0fca94c2f5cb878ddca5a17a"},{"version":"26.2.0-alpha10","sha1":"8f3f0e1008062c8ff34cce04a32ea6c16c493bd8"},{"version":"26.2.0-alpha11","sha1":"eb6600a87df0d237c1dc414e3b8ea41a6a517830"},{"version":"26.2.0-alpha12","sha1":"f533c43ab3ece28abacd518c5bc0dd45b182b4ae"},{"version":"26.2.0-alpha13","sha1":"5dc7f94ebf867f91331f3cef51194e17b25631d5"},{"version":"26.2.0-alpha14","sha1":"2a8e0d571d3761f04f3a2885b61fbacb23444ed8"},{"version":"26.2.0-alpha15","sha1":"7986727fd80d076a519bd78bde0f189043db9d1e"},{"version":"26.2.0-alpha16","sha1":"d9917298b77c786d79bfbae106d298c56570cbec"},{"version":"26.2.0-alpha17","sha1":"ec2140f478e3688b6f0112366b18e2fef1c9d00c"},{"version":"26.2.0-alpha18","sha1":"88b188daecf46103f96524baaf9766cb2c32a0e"},{"version":"26.2.0-beta01","sha1":"7042edcfc6890c587587aea4c1525073d5cca472"},{"version":"26.2.0-beta02","sha1":"b211f0894829936d45f41cc12d2695395fbbdd91"},{"version":"26.2.0-beta03","sha1":"918e99819c192027402ca51814534cfd4b3acdab"},{"version":"26.2.0-beta04","sha1":"8ff233485634e5bea2d0e32603175357614562c4"},{"version":"26.2.0-beta05","sha1":"6b62574937ec2af57c86ac89f1e5f9a730a8f7d6"},{"version":"26.2.0-rc01","sha1":"d60aca61decb154fbf5773a2281d08f61b07ac7f"},{"version":"26.2.0-rc02","sha1":"908e58a3d47e25f1f934a5f10ae52192fcf27992"},{"version":"26.2.0-rc03","sha1":"88c6504075a83e70fd5142b432bd5c1d11a7ca53"},{"version":"26.2.0","sha1":"fc9e3de435eed4c659ba8f587b8c011de56fb280"},{"version":"26.2.1","sha1":"ccc983c79f58584df16b7a0df7c20a57754cceaa"},{"version":"26.3.0-alpha01","sha1":"2660805b196c92af1099a1ba88f8000f30a47837"},{"version":"26.3.0-alpha02","sha1":"6e0071c6be197b72471148ede084dce0f0c0d546"},{"version":"26.3.0-alpha03","sha1":"927db1e91302469030007f9c3f301d1c5e3c0599"},{"version":"26.3.0-alpha04","sha1":"32604c1035396b560f01789c646311f32b764fd4"},{"version":"26.3.0-alpha05","sha1":"77e26b97468e1d1cf01a2a650363882c70130553"},{"version":"26.3.0-alpha06","sha1":"3cd132f8d2a3888908d079ff07d3cce9ff640635"},{"version":"26.3.0-alpha07","sha1":"ad35c56d64da3d28aaf2a8a9286bc77a5e9097d2"},{"version":"26.3.0-alpha08","sha1":"6900e900f47499ecc228652d6a9e8713c32c03ab"},{"version":"26.3.0-alpha09","sha1":"fcfd798d5d6e7cf00d49a4a5411317738f5f8ff6"},{"version":"26.3.0-alpha10","sha1":"e5423429b57fdcd1d8807b2f458b6dac509440be"},{"version":"26.3.0-alpha11","sha1":"7d83573caa080924030e8977e8e5c65c45c9bbf2"},{"version":"26.3.0-alpha12","sha1":"2b66ad9a8aede7a8a171fb1b8288d12ec9d4b401"},{"version":"26.3.0-alpha13","sha1":"8880495cbd330448273c2ec48f5de3cfa34dbdd5"},{"version":"26.3.0-beta01","sha1":"ddf4f67c8731acbe0e90e823ec56e3de2e2047ca"},{"version":"26.3.0-beta02","sha1":"d8437d6cb0f91465ee70f8e678509ab6b3b8692a"},{"version":"26.3.0-beta03","sha1":"d52ec585fb4f244a3d77afcf074df7d95dbee15e"},{"version":"26.3.0-beta04","sha1":"97399eb514e0ff3ac68a29f0f1f86547c038f4c4"},{"version":"26.3.0-rc01","sha1":"b1beb8ef3f8ccfc44d0f46156dc3b1504988596a"},{"version":"26.3.0-rc02","sha1":"f17a41d85cfbaf102a92d6684aa432de41d8cd08"},{"version":"26.3.0-rc03","sha1":"126f9c9e967710b0083a8b27164aba439ffd65db"},{"version":"26.3.0","sha1":"88ec00a74ebee24bad63c6f5fed45ff18780133f"},{"version":"26.3.1","sha1":"57b2192f22cc8c38c09a0a24032af3164b1853a6"},{"version":"26.3.2","sha1":"dd065a40564758b854a212c1caae199e7ed77927"},{"version":"26.4.0-alpha01","sha1":"19432f910d5b7df82ccbceb7cf7eac036af28065"},{"version":"26.4.0-alpha02","sha1":"4f9a9e265a8e35202c6d0e0b6a415bdbe3bb3d30"},{"version":"26.4.0-alpha03","sha1":"a501e236d9d4078fdec4ea0bf12e17c11f2e01eb"},{"version":"26.4.0-alpha04","sha1":"582f40a6b4897c51978d834ef143cc3b8e57b7ea"},{"version":"26.4.0-alpha05","sha1":"60108a742a72786237059372de4f34d69a189c5e"},{"version":"26.4.0-alpha06","sha1":"9b292a2d273a734e640df0ce857abe5619074aff"},{"version":"26.4.0-alpha07","sha1":"c7f3ea4eec3cb0c9158f4e48acfba4c2db16296b"},{"version":"26.4.0-alpha08","sha1":"c50150cc9b457c98d2adbd2822191c626f2b6a6b"},{"version":"26.4.0-alpha09","sha1":"c6a0f9423929cccf461fdbce7e4c99cb45a72efc"},{"version":"26.4.0-alpha10","sha1":"b4470333d2e5bfbeea91389c7d43b78b7d5df855"},{"version":"26.4.0-beta01","sha1":"a6c41cb9ca23b920a948d35a2eeee4d4539fd7f0"},{"version":"26.4.0-beta02","sha1":"e29a8ed7c076e3b0460227f0ded407dccac2cb4f"},{"version":"26.4.0-beta03","sha1":"e69e7b3eb5523a4fbe25f1a8832148445f165d94"},{"version":"26.4.0-beta04","sha1":"1d6e43755339fea4fd1ddab5dfe47fed37630781"},{"version":"26.4.0-beta05","sha1":"85befed51d657c3f1e888f2c19b1bc97ef0a003f"},{"version":"26.4.0-rc01","sha1":"627207c41a304b98815ee43d1cb7f46b11a91bca"},{"version":"26.4.0-rc02","sha1":"631745e2dfe9b0b58094121a1be63eaef630db0"},{"version":"26.4.0-rc03","sha1":"b877df57a88653b08bd29b5e9be227f47c74c752"},{"version":"26.4.0","sha1":"693d8f2e4dfcb4d4701f895196e4494b212336e3"},{"version":"26.4.1","sha1":"5f0c9cbb3dacdfd7137b838d332094d62c80d653"},{"version":"26.4.2","sha1":"faca73f15f1a132e319690328df154d975e31ece"},{"version":"26.5.0-alpha01","sha1":"bbf5d8d026d55c25e880e5025ad32e93cef10f9a"},{"version":"26.5.0-alpha02","sha1":"76a42ac2f07b5fb8b1057911721616ccc7a1e1a1"},{"version":"26.5.0-alpha03","sha1":"49ce7bf75368fd7d416bcc78b4dcf88e2b3700c"},{"version":"26.5.0-alpha04","sha1":"a5546cbc75426474ea9a0780fb0d2cf2ba984c7a"},{"version":"26.5.0-alpha05","sha1":"f121920bf89f9eebd8d278f7c362444a826f985"},{"version":"26.5.0-alpha06","sha1":"ce6f6b1494f4ce08acf48a867efc9b9e5b9963e5"},{"version":"26.5.0-alpha07","sha1":"81299ebdacc2a94d9b0bfe5de2712ba90c938e4e"},{"version":"26.5.0-alpha08","sha1":"b59374351aebac5c2f3d824eb3435df2771d3a70"},{"version":"26.5.0-alpha09","sha1":"f8ba1924a34810b36246a793a1c74f3310ac888c"},{"version":"26.5.0-alpha10","sha1":"cada69856737ad4acdc7f8dcfa04c28b0f108d1e"},{"version":"26.5.0-alpha11","sha1":"b741a311aee931b3903a461dfc454d2387c80a6a"},{"version":"26.5.0-alpha12","sha1":"50a6ee3dd259afe8278fedbd6076efcec2e89397"},{"version":"26.5.0-alpha13","sha1":"c38df4e347d543d3abab1946fcc954187f89d1c3"},{"version":"26.5.0-beta01","sha1":"7c72ebbaa7e1c10cace6b04f5cd03a770e6fd395"},{"version":"26.5.0-beta02","sha1":"80d8612ca5637376458ae52f83189b07584b077c"},{"version":"26.5.0-beta03","sha1":"e839740281c4b3a228c0df12ff7bbb07a711fde5"},{"version":"26.5.0-beta04","sha1":"47106528dcf0e1ae88bdf2dce4c52ed1ec812588"},{"version":"26.5.0-beta05","sha1":"7365fa46d911f5d3999d73905c50a7d7b13a6930"},{"version":"26.6.0-alpha01","sha1":"a92f9a74af70a71f4a2728332b73e627e3d9d6e6"},{"version":"26.6.0-alpha02","sha1":"4eb46b1c945f50ecc375c5e3585a9e965b542406"},{"version":"26.6.0-alpha03","sha1":"5aace8083bb007e054899587d9c8664d7c0b50a6"},{"version":"26.6.0-alpha04","sha1":"9357feba1350184787f92e8476eaeb3380e4ce0d"}]}]},{"group":"com.android.tools.build","update_time":-1,"packages":[{"package":"gradle-experimental","versions":[{"version":"0.11.0-alpha1","sha1":"20cb8e661454d87c3bc2c8041a0a260bb6233c54"},{"version":"0.11.0-alpha2","sha1":"a8043593d385d4f5541b733a06ce48f98bc37196"},{"version":"0.11.0-alpha3","sha1":"35fd25f88588a8d42c5eae6e8033fc7d12f12709"},{"version":"0.11.0-alpha4","sha1":"af834e8344ceac5ccd436751e6e030facfd933a5"},{"version":"0.11.0-alpha5","sha1":"139c159bbcb30d049dcd14c173e9562071fd6633"},{"version":"0.11.0-alpha6","sha1":"da12bb295edca744e89fbe7ccd7b49c66acd03b8"},{"version":"0.11.0-alpha7","sha1":"a021272c28a6428a02a883dcf495b976c002c791"},{"version":"0.11.0-alpha8","sha1":"a50a56a7cb830280a067676f1f3cc47252d9312b"},{"version":"0.11.0-alpha9","sha1":"2ea65dcf86af14cb247dc47fcad2b9494d88c669"},{"version":"0.11.0-beta1","sha1":"fdd227388b6beed8829880fe2145381bd84f1ea2"},{"version":"0.11.0-beta2","sha1":"e45e294385749a2e00258bdbd7ba1cbe6337b95c"},{"version":"0.11.0-beta3","sha1":"b6d8147a7dde784f1ebd948579d7f6301511ec20"},{"version":"0.11.0-beta4","sha1":"4e53c6a00398dd2c96229e6db9e4468ab52ad4a0"},{"version":"0.11.0-beta5","sha1":"2d4ff3156783d7b235d808da83fdd4a666c86199"},{"version":"0.11.0-beta6","sha1":"9a68bd0900b30827a847352806329614adcf1c71"},{"version":"0.11.0-beta7","sha1":"7dea760f7ca9da328db092c0bae9be397aca6dc2"},{"version":"0.11.0-rc1","sha1":"15d86409621a9d51a931ed2294faff487c7f1860"},{"version":"0.11.0-rc2","sha1":"aec4b85d5cc80a2accdfd09f8cab16c0e8cdab66"},{"version":"0.11.0","sha1":"b97a962cf8cb26eac79f789b8b4e42b4062d233b"},{"version":"0.11.1","sha1":"4ed1be88f7c42eae37feda2ffbdb6af1ae3dbfc9"}]},{"package":"manifest-merger","versions":[{"version":"26.0.0-alpha1","sha1":"29929be366665a6ee48e938e5a5f344ae497b313"},{"version":"26.0.0-alpha2","sha1":"20988fb3b8a78b95da1cd95c48afbced5acf52c1"},{"version":"26.0.0-alpha3","sha1":"13c4050ff1f0926121bce91467bea530629b3794"},{"version":"26.0.0-alpha4","sha1":"2a55c240f38c97875c6180a8a9f12da5e0e747ce"},{"version":"26.0.0-alpha5","sha1":"8e7f5c0cba148ec79a86cffe2cadf58d2c3f634a"},{"version":"26.0.0-alpha6","sha1":"d8ad5e6be5a2dfba4e9cdb1c34d124b85ad2eda3"},{"version":"26.0.0-alpha7","sha1":"e7ee481b362a36c3e1577ecf0e13e4be62ef5e68"},{"version":"26.0.0-alpha8","sha1":"3208424ddbc196092b2a9ff4206eb49ba5fc092c"},{"version":"26.0.0-alpha9","sha1":"5958b94cb15ef30aeec5f12e600cd60714023cf8"},{"version":"26.0.0-beta1","sha1":"5d813e55b17d2cae38006b2a9c7d92ec3c2c9317"},{"version":"26.0.0-beta2","sha1":"bd001825358089d25ee568ae37a603e2ea735d31"},{"version":"26.0.0-beta3","sha1":"f44fe59de9fb9d80800c138a4c9a799a27f1fe7f"},{"version":"26.0.0-beta4","sha1":"5f99331442f7a3fc8b883787b4bd1141a91a913"},{"version":"26.0.0-beta5","sha1":"a8de364314f6a2c8c01cc66d0b833d873b473188"},{"version":"26.0.0-beta6","sha1":"9a0e942212a1df6ed4ecd5211babc05bd8739ab8"},{"version":"26.0.0-beta7","sha1":"9017111539465d69c141e1aaf78066a6568ad5e"},{"version":"26.0.0-rc1","sha1":"957ca60192b817a051b1437e95c7393138d1f1e8"},{"version":"26.0.0-rc2","sha1":"32d1073640b27a7227fdf2a2308afdc12f280251"},{"version":"26.0.0","sha1":"f625bcad5f5dc0cb373e6b62595d64a552a8d073"},{"version":"26.0.1","sha1":"700cbb1ed4066f48d4970f5669c44a51bd03142"},{"version":"26.1.0-alpha01","sha1":"f81599dcbc2d58213ae36b43631aaa33134e5198"},{"version":"26.1.0-alpha02","sha1":"4ea79925a55146f64fe6267269a06af40b2107a4"},{"version":"26.1.0-alpha03","sha1":"6c151878c8b38b258bdf57ca740fd21b07098411"},{"version":"26.1.0-alpha04","sha1":"244e8b76aa8739a499db528bd9abbaf648b3eae9"},{"version":"26.1.0-alpha05","sha1":"92dc470a77e7dabd90f70a00c16eb9804bf4e6a3"},{"version":"26.1.0-alpha06","sha1":"f74d3405f7e99c5d87d84bdc5f11a250907aabf"},{"version":"26.1.0-alpha07","sha1":"7ecf9283f1d8424619dcab02d780a4456d8cd196"},{"version":"26.1.0-alpha08","sha1":"cc2df21fa0687e9a455adbf9a0102fddba5fd408"},{"version":"26.1.0-alpha09","sha1":"c01002bbbaf18b76e1e7a25a4426b3c9beaa2dc0"},{"version":"26.1.0-beta1","sha1":"bbba1cdabd2aa7b9a4af9b645497e0fccb51430c"},{"version":"26.1.0-beta2","sha1":"c47d7833b5cf66ae550b3de876f963ac9bf0e507"},{"version":"26.1.0-beta3","sha1":"63b55e4e909b387777a9a159443ecce27235db64"},{"version":"26.1.0-beta4","sha1":"3c87615e84d045684df69d5f2e6a8781efb349d6"},{"version":"26.1.0-rc01","sha1":"11c016f3540296eb72cdf1b24c17827f5d4c64be"},{"version":"26.1.0-rc02","sha1":"b015dfc59ef8c4975e35d91f540d992c9b169e90"},{"version":"26.1.0-rc03","sha1":"2501cf19c809fac1c406726d7dec20726c9e6791"},{"version":"26.1.0","sha1":"1c9ca4847077e959b8cc5f97fd64dfa4b75d5218"},{"version":"26.1.1","sha1":"3d36a633e8e562b0624e67304ca531d8264ee051"},{"version":"26.1.2","sha1":"79f398427650c76f0c66c89f10e4886a1fe68c26"},{"version":"26.1.3","sha1":"ab6e289516f794bc39862cc969961c7ec9246cf2"},{"version":"26.1.4","sha1":"ddb4dcb3bb44c7053fa583967dfa9030f43f1c01"},{"version":"26.2.0-alpha01","sha1":"6bb5adfdcbb74031a2307b35ef36ef71bd083b25"},{"version":"26.2.0-alpha02","sha1":"1305539d462389064189c78608441e713e7561a4"},{"version":"26.2.0-alpha03","sha1":"b0cc4224e9691ad29fa2b58c10655853e0504d78"},{"version":"26.2.0-alpha04","sha1":"a72b5b24bb9a9bcdb7876135639c98eddb51e7a4"},{"version":"26.2.0-alpha05","sha1":"2074a15cfa7f5ceef5101f8ca51f7c68423b2d6d"},{"version":"26.2.0-alpha06","sha1":"46eff7bd13299c9babb7e0a976592730fe2fb198"},{"version":"26.2.0-alpha07","sha1":"c172a5c1d4b23422cd4c048c24dfa50a8f664c3c"},{"version":"26.2.0-alpha08","sha1":"7c035dbd463feb8dd4e1b7041a464440aa290698"},{"version":"26.2.0-alpha09","sha1":"e5fed099159bf4d1e859e10f7732c63783feb04b"},{"version":"26.2.0-alpha10","sha1":"21825f5dc7113d216048b35ea21c802b99ccb834"},{"version":"26.2.0-alpha11","sha1":"7e3d96e4aa58f548d98915f3331c3e99dfc78976"},{"version":"26.2.0-alpha12","sha1":"b1a2f9e6a35bbecdcfb8f5aa29702df9bad5ce75"},{"version":"26.2.0-alpha13","sha1":"15ef6a795cee9e55f844097c7ab0c1940c6309a5"},{"version":"26.2.0-alpha14","sha1":"e6d281cca8922cb025e8e1f449bb5ca0b9cca81e"},{"version":"26.2.0-alpha15","sha1":"ff87d6850584daeb51062f40b7e6c40c15656753"},{"version":"26.2.0-alpha16","sha1":"a9424918934b70619f69bc164557eb4f2f86becb"},{"version":"26.2.0-alpha17","sha1":"60688683e6f0bf512f464daee7ce9c44668804b"},{"version":"26.2.0-alpha18","sha1":"ee546aa6f59eba771c76bdf19693ac2948e762f0"},{"version":"26.2.0-beta01","sha1":"1060bf858a95060b78f58ecc1e42336aa4bdc93e"},{"version":"26.2.0-beta02","sha1":"ba73c198eea9372e302894bc1d622940845fa1cf"},{"version":"26.2.0-beta03","sha1":"eb0b73940fe2ac5ba9c57c7fc3bd3c4a689e2c6"},{"version":"26.2.0-beta04","sha1":"e6b59501489080699f9c6a87628214d2cb5ba817"},{"version":"26.2.0-beta05","sha1":"9b17c510d9f2f1a53737ea16fed6d0e937d841a7"},{"version":"26.2.0-rc01","sha1":"8caafb3731eac8497bb67c107316f0e7a675312f"},{"version":"26.2.0-rc02","sha1":"8032f530ea8dd64356be8a4ad28afa1677574069"},{"version":"26.2.0-rc03","sha1":"c2108959d70334c175af71f89c48ee59046444e5"},{"version":"26.2.0","sha1":"3106a46f02adefebb49cf20b8c8c31e8b61c453b"},{"version":"26.2.1","sha1":"6a6542caf30fbc4091dffea76c602e97bc9d8949"},{"version":"26.3.0-alpha01","sha1":"8540424ce7671a9000cf175b6ea5f0fa5543ca8a"},{"version":"26.3.0-alpha02","sha1":"737e1cd020d2a875698e229d9fbc4f20a9110e46"},{"version":"26.3.0-alpha03","sha1":"ea916f1329aed2b678a191c10d8642b93dcb7639"},{"version":"26.3.0-alpha04","sha1":"a03ea1c2510f5e60ff3fc90c3cb901e0521f53c3"},{"version":"26.3.0-alpha05","sha1":"835bdcc56a04fe13ea5ee9e41314fe3a8b134cba"},{"version":"26.3.0-alpha06","sha1":"6e1d3aeab7f2854252db4bca3657e23d1d6c6d14"},{"version":"26.3.0-alpha07","sha1":"291c640bc901ddb2ea342628161a5592779bbec0"},{"version":"26.3.0-alpha08","sha1":"18687b6a08ed3605f7bbffa33d8b576767fa78e2"},{"version":"26.3.0-alpha09","sha1":"ebe5dd722c7fee2458babc2bb9d6540f04609663"},{"version":"26.3.0-alpha10","sha1":"b3eaf37910b33341534c00b6cdb306e2005bc924"},{"version":"26.3.0-alpha11","sha1":"99dae855c7457057c1ed045bb7a45f78850bbccc"},{"version":"26.3.0-alpha12","sha1":"97bad0b562653e86908d6e29718cddf4295c470b"},{"version":"26.3.0-alpha13","sha1":"c45647b7daec3b551f5bb59d5384db1bcf7f1b33"},{"version":"26.3.0-beta01","sha1":"bc7bab49c6b2ee1d28422bd37b9ebd8ab2e83bc7"},{"version":"26.3.0-beta02","sha1":"e5eb695a2ad063cc2c363e6702176c373b6db87c"},{"version":"26.3.0-beta03","sha1":"10d379cae409788ba9e215e783ada8f44bcfa5a3"},{"version":"26.3.0-beta04","sha1":"797baf6d80ee1c642380ff9a09722bd4c19bd4d0"},{"version":"26.3.0-rc01","sha1":"6ca64001af1c713980b6bb16937b24b36e91c251"},{"version":"26.3.0-rc02","sha1":"25d00a44206e268c1bf2c7d1b337ffbce2dd97fe"},{"version":"26.3.0-rc03","sha1":"ba2555595ab47a9f4cbe5423169af3f0f7ba900e"},{"version":"26.3.0","sha1":"c5d936d8564c06b578d3e3f27ded1022a55eb93e"},{"version":"26.3.1","sha1":"aa94f738e318fc73059d9207f433f13556072d05"},{"version":"26.3.2","sha1":"dd8b58e14f2d3b006bbbd9d46b7abaf707930a5b"},{"version":"26.4.0-alpha01","sha1":"8b134ce167bbc62145af4aeafc90a336d0c63c34"},{"version":"26.4.0-alpha02","sha1":"40e8a38aa34bd8869e9bd60c13c04780f9f3c4"},{"version":"26.4.0-alpha03","sha1":"17d85c2c7a24b248eb98db4d8425cabf520f8a4c"},{"version":"26.4.0-alpha04","sha1":"e22718d93a104f1711ecf8c35d97f029d87c2bcd"},{"version":"26.4.0-alpha05","sha1":"3b00965180df0e6680778365db548073c5eb55d7"},{"version":"26.4.0-alpha06","sha1":"2b432bda48e3b151459408b281a85ca56dd3f43f"},{"version":"26.4.0-alpha07","sha1":"1ae37a6e62f47668828b8a1ba1e321fb718b8c67"},{"version":"26.4.0-alpha08","sha1":"698ff49ba221694ee0e1749c05d7fbda6da5ad9d"},{"version":"26.4.0-alpha09","sha1":"d8ab131ceb8b385e1c05ad60eb25fef755a6f9ba"},{"version":"26.4.0-alpha10","sha1":"152e54a8e08904772f9ea0bbf1c8a0edec27e3bd"},{"version":"26.4.0-beta01","sha1":"ff2220abe3611111116f244876a8f24e1de6d47a"},{"version":"26.4.0-beta02","sha1":"7aff5eac2cc9b8f4349774abc6647f95eb205533"},{"version":"26.4.0-beta03","sha1":"68c2b44cabb85000da502adbf898f4b2e283e8e1"},{"version":"26.4.0-beta04","sha1":"5e2b973a491efb9001e83e1cb32e70aa990c6128"},{"version":"26.4.0-beta05","sha1":"b1025bbbf13c6b5c33c642d376e4343b396301c2"},{"version":"26.4.0-rc01","sha1":"dc7816c35f106ec97e2799332784cad9d65bdab7"},{"version":"26.4.0-rc02","sha1":"c5fff93bf8617cb127aa7071d216fc45229ddd1b"},{"version":"26.4.0-rc03","sha1":"b6d8f5de40933c913e13ff114a22af3d7430f07"},{"version":"26.4.0","sha1":"97ad7348b1c018f304378c9b72094fb9cbfe6433"},{"version":"26.4.1","sha1":"653712f9996c0cb1f6b721a7712c2084b9dc28eb"},{"version":"26.4.2","sha1":"947fb2bcf86c6e51be609a1e6ee723c2bb074348"},{"version":"26.5.0-alpha01","sha1":"66df526965d8cc4b88943b66e906011bf7b2867c"},{"version":"26.5.0-alpha02","sha1":"8129a34246ce9275066ad2d32a21936f73012d5c"},{"version":"26.5.0-alpha03","sha1":"7e77b04a02562c756df10a7d2e27120f5fe29d66"},{"version":"26.5.0-alpha04","sha1":"72b95880d4325e7ca3ee91c1ba60783da0bbf903"},{"version":"26.5.0-alpha05","sha1":"9d596c95e402057abd40faba8fc4c13254e94ee1"},{"version":"26.5.0-alpha06","sha1":"c1f94efe218b6d4738117ecf106a37ee9601a18e"},{"version":"26.5.0-alpha07","sha1":"e34e03b90aa53e5a1b2de84f5726073f04f81a31"},{"version":"26.5.0-alpha08","sha1":"8a30532a3c901f42255cfd86108ed4bc7934240c"},{"version":"26.5.0-alpha09","sha1":"4bd59c3f23f12ced261ab16836046a54f5f98d6b"},{"version":"26.5.0-alpha10","sha1":"7c2b5020f175591bef32a27589e820630ab24790"},{"version":"26.5.0-alpha11","sha1":"d4c5ef096351e75e33b5223905cc422ef22f417d"},{"version":"26.5.0-alpha12","sha1":"b6d69b0d22fcbe9ebf1bcd4190b4c41099339a2c"},{"version":"26.5.0-alpha13","sha1":"e52534caca7efb9cd6e7c23e8911fabfcb91208c"},{"version":"26.5.0-beta01","sha1":"31a6036ebb3cfde4595f9ca5fc059863a04d8c4c"},{"version":"26.5.0-beta02","sha1":"bb1affd05b22736de7eac0be0d9dc3ee972de72d"},{"version":"26.5.0-beta03","sha1":"ed511552fead92d673afa43a57a1c79e33187b11"},{"version":"26.5.0-beta04","sha1":"f0784f37457c26b50eff68d73fd87913b46b1d3d"},{"version":"26.5.0-beta05","sha1":"76a0696b72340382587f5946ef9ae06d8c5c46f7"},{"version":"26.6.0-alpha01","sha1":"3bf7eb3a324751804bb7cf956a0b6cc6b13bfe77"},{"version":"26.6.0-alpha02","sha1":"8ff43718ef553cb2791572fd784c28826d0c6926"},{"version":"26.6.0-alpha03","sha1":"43cbc4ce2c066c2181813c15103d9fa84688e1e1"},{"version":"26.6.0-alpha04","sha1":"f8df08644745e469ee63250c5a6a49e6b22a344"}]},{"package":"gradle-api","versions":[{"version":"3.0.0-alpha1","sha1":"a9faabbde14bfc01c8dc3ab0d62a92e8b677cdfa"},{"version":"3.0.0-alpha2","sha1":"9ec3e0c8c2781772a73c9dbea16eafbce445974c"},{"version":"3.0.0-alpha3","sha1":"74bfdba32ea30b866d42329c6cd105220d43ac74"},{"version":"3.0.0-alpha4","sha1":"4e864cfe79b0cb51a0374d73b05345a8b277c24d"},{"version":"3.0.0-alpha5","sha1":"3a2b05323b6be369437402c772a280966620bac4"},{"version":"3.0.0-alpha6","sha1":"f3dbec0e5778cce5062681ee522e11bdd556f4e9"},{"version":"3.0.0-alpha7","sha1":"e43f36c4b6b5b2f2df80a5cc0e836a3f73d01438"},{"version":"3.0.0-alpha8","sha1":"a16d531fbd7b05e1e5e6adbd81e84c18713dceb8"},{"version":"3.0.0-alpha9","sha1":"d7cf4e8d62ffca042a98d3a832be0b90525c47c0"},{"version":"3.0.0-beta1","sha1":"2c2f7365edeb5e0bddb23ccf5fe8737b5939fd70"},{"version":"3.0.0-beta2","sha1":"4aa065e294cc6ac3ce331fb70a4aebbb40b1d25d"},{"version":"3.0.0-beta3","sha1":"1ce8d7448e257c7c0bf5e235d61f988811f98733"},{"version":"3.0.0-beta4","sha1":"ac7607865c91922d64f71bf56d8a77075ffd8a4f"},{"version":"3.0.0-beta5","sha1":"a4b67f659c5b2aae65bddf7596d0870342b08827"},{"version":"3.0.0-beta6","sha1":"a061cabb6bee3380a7ecabb51308bc6a81e2a3d5"},{"version":"3.0.0-beta7","sha1":"c428f5c20f42fa7da9f66a366aee85a6f8a6606c"},{"version":"3.0.0-rc1","sha1":"324975f92900ac30d90c5a94ff211999f760b1c1"},{"version":"3.0.0-rc2","sha1":"fe27622e5b53bb870137cc1984df654341983466"},{"version":"3.0.0","sha1":"e98ade5c308a99980d2a61f4ce1d9286df0105e3"},{"version":"3.0.1","sha1":"92640ae3c543aa3faee7b954ce641d03949d4401"},{"version":"3.1.0-alpha01","sha1":"53392a7f14071b1d5501e2039cf7c3e274368fd8"},{"version":"3.1.0-alpha02","sha1":"1218d37b3174d3bb935337be872b3fa60b74361c"},{"version":"3.1.0-alpha03","sha1":"b7509810fbec3543a7073b73a3beee5124b3dd43"},{"version":"3.1.0-alpha04","sha1":"692c79d63da8fda95be06da66c6fd8e3f8a9b3b"},{"version":"3.1.0-alpha05","sha1":"e668f737e7f50dfb460aa9b07ff9a2e74b6e4b13"},{"version":"3.1.0-alpha06","sha1":"97394d2a4a41651e633f7637b8fee4521f2306b6"},{"version":"3.1.0-alpha07","sha1":"f23a7efa66f1e1e4e18c6190807fda48d9335509"},{"version":"3.1.0-alpha08","sha1":"a12c5cc654107014a6190098ecaa1ee140650e98"},{"version":"3.1.0-alpha09","sha1":"7aa49146b7e2525a51fc17796e6abf2265579d8c"},{"version":"3.1.0-beta1","sha1":"cf1e8a9293d0cd2489c6222781649d3a026c0c1b"},{"version":"3.1.0-beta2","sha1":"f2460d47a8271fb828f66ab9d4e4119094400223"},{"version":"3.1.0-beta3","sha1":"15f22bf1bc08ef0a0e56ff654ba6cbc2c977320f"},{"version":"3.1.0-beta4","sha1":"fa9fb6ce0e34ede108d4fe66d38b9ec2b106195a"},{"version":"3.1.0-rc01","sha1":"ee86ba91e2157970c3d7ad277bce077dcc3cf425"},{"version":"3.1.0-rc02","sha1":"b45145e01df1dff6fecd010aa4b2d8d259260068"},{"version":"3.1.0-rc03","sha1":"ca4aeb607dccbfedaa2dec96418f4a1fdefa5d9a"},{"version":"3.1.0","sha1":"ecd0b656862fdf1288df7fc06cd2172e423704b3"},{"version":"3.1.1","sha1":"2d1a2b6775a3a3c9d7ca3488ca9479f5b05e4e96"},{"version":"3.1.2","sha1":"427e25639a55911cadcf70657c9b2ded2ad6af2b"},{"version":"3.1.3","sha1":"9e271de6863b6fe0ffec24dbc0106f478c86f869"},{"version":"3.1.4","sha1":"eb41dfb5596afd8933c804595ca8596952fad450"},{"version":"3.2.0-alpha01","sha1":"288f37116f78e8410902b6a85a3a03013124e1e3"},{"version":"3.2.0-alpha02","sha1":"86f19ea7dda1ecd90951d8297d40b298168b8421"},{"version":"3.2.0-alpha03","sha1":"cd06ad9676eea50c11a7677f7e471f241541229"},{"version":"3.2.0-alpha04","sha1":"9d3aa6c21696fe99f1dcaaa83a4d31f3f7a31a05"},{"version":"3.2.0-alpha05","sha1":"2cee0ce01d88cb5b385d1fc9931653baf4a3b18"},{"version":"3.2.0-alpha06","sha1":"209b3b48a45d099fd08e65aa91334fccd64326ea"},{"version":"3.2.0-alpha07","sha1":"a25aa80d40961859906410641886282f4456490f"},{"version":"3.2.0-alpha08","sha1":"c86be68cf9b409c5de62d15ef20bf07aedd95fa0"},{"version":"3.2.0-alpha09","sha1":"744de801b46fe09b8043cb00b9aa1b98312473be"},{"version":"3.2.0-alpha10","sha1":"9f6e8b45d8689529e801b19d5c2e21b6020b685f"},{"version":"3.2.0-alpha11","sha1":"3331e68da82571a2439652536bdc90773338d37b"},{"version":"3.2.0-alpha12","sha1":"f9e467e3c3f599ffbb67dddfc7972f723711ecc6"},{"version":"3.2.0-alpha13","sha1":"7625f93706f736bbb6021c4a45ca628ebc30dd6"},{"version":"3.2.0-alpha14","sha1":"ee1970e06a1e28db9ead9dce805178b29054d985"},{"version":"3.2.0-alpha15","sha1":"946cb623d90160c6174c57619d4e043e3e10a8f2"},{"version":"3.2.0-alpha16","sha1":"a3833279d34e6795b141300f71478f69988492f0"},{"version":"3.2.0-alpha17","sha1":"4d21fae3d2d9e0b52e05df6971125091d72ec833"},{"version":"3.2.0-alpha18","sha1":"cbabc54b8eb049c1ffcf79c1b39e9ae0eacf35b7"},{"version":"3.2.0-beta01","sha1":"df9dab5ef6bd3bd6dc45a2caf7986b465e6a150c"},{"version":"3.2.0-beta02","sha1":"314610d057223906b972a59d51ea73f9b720f5f1"},{"version":"3.2.0-beta03","sha1":"93e23b152fcdae98e47b62b253727fdf54a7f750"},{"version":"3.2.0-beta04","sha1":"4770e43db13583a1e6cbf4691733caa9e5e2a8c9"},{"version":"3.2.0-beta05","sha1":"97de96555a0deb633f34700504ec76ec3552a0b4"},{"version":"3.2.0-rc01","sha1":"a5df37e9fba957e2fa8b5c7ed957787e1170a8fd"},{"version":"3.2.0-rc02","sha1":"56fc82069c6707652c2a36375afc3011d69546c2"},{"version":"3.2.0-rc03","sha1":"78e33e4f2ad8ec413529f38653936d6e8f05c771"},{"version":"3.2.0","sha1":"7f8d5fdba9ac08d227abc6f7e034a86a1b5c9c48"},{"version":"3.2.1","sha1":"825438df08a0c323b7d315071d402ff8fe700a42"},{"version":"3.3.0-alpha01","sha1":"2651af96f7055c5d4ebd26b9985216656b4f42b1"},{"version":"3.3.0-alpha02","sha1":"ae4219cba278f589a829b7b83a005db02776b179"},{"version":"3.3.0-alpha03","sha1":"cac2c5a15d652a99aebcd560034e262a6f8bd311"},{"version":"3.3.0-alpha04","sha1":"1b3edf5b993820144bdc8a5f05ffe2a40d915fdd"},{"version":"3.3.0-alpha05","sha1":"a293df608cf9e86477aa9535a2fcd04f12a221b"},{"version":"3.3.0-alpha06","sha1":"6a7907b4ff206d9e93cf37e26b164a36a233f333"},{"version":"3.3.0-alpha07","sha1":"2e3738c9fb970c65b0efe3afb9a47b294bb8f461"},{"version":"3.3.0-alpha08","sha1":"661cf8cb0a01945abc9fe0816c036b80d023fcb1"},{"version":"3.3.0-alpha09","sha1":"711ca3406cbc00b032382d0c4d54554a98069f7b"},{"version":"3.3.0-alpha10","sha1":"a981ec35fa51fe0f9e370f363423042bc98e03fc"},{"version":"3.3.0-alpha11","sha1":"bb805553fb094c3bbea50b7a559b5ff33c8ea76d"},{"version":"3.3.0-alpha12","sha1":"d938a0bdd82c5ce369a847eeb60b2ab765981a48"},{"version":"3.3.0-alpha13","sha1":"70639143bc8bc4aa383435c301791e5043c86ed5"},{"version":"3.3.0-beta01","sha1":"ef8059537f524e7eaab30d0612944ad06cb17959"},{"version":"3.3.0-beta02","sha1":"507cb08b4571f6a39fed0210b5051a615b59df57"},{"version":"3.3.0-beta03","sha1":"a67df8550d27b910a96cade04999117808f9ef3c"},{"version":"3.3.0-beta04","sha1":"690961441bb816dec770d08247c05d514c17a8ae"},{"version":"3.3.0-rc01","sha1":"2e18e329b8831d9ca45557d5b4a8ae1357396f54"},{"version":"3.3.0-rc02","sha1":"e0dcd26ca6b04addc2e8c7ea548dc0a2ec1950c4"},{"version":"3.3.0-rc03","sha1":"a6b2ade2183b02f2a68086bc4fc060933ea4336f"},{"version":"3.3.0","sha1":"a30fd449a65dff4a03284ad5daed5588435306af"},{"version":"3.3.1","sha1":"93aac19942998e480db4195212150fb30f7a78cd"},{"version":"3.3.2","sha1":"edc6d3824d91554590c08304893622ff703eb2f4"},{"version":"3.4.0-alpha01","sha1":"f52668e9b089ec5ee7224e5f0e8d8bae2e71f60e"},{"version":"3.4.0-alpha02","sha1":"fe044bc72ff2c5a7259be708c90b3fd703274b2c"},{"version":"3.4.0-alpha03","sha1":"2666bc92316eab0c26b6372036f59e0ef16f9d95"},{"version":"3.4.0-alpha04","sha1":"9720f038be61d71af01dc891c1cec2d506a0e957"},{"version":"3.4.0-alpha05","sha1":"467dda5279e5a77708d971f3d35c3cc3dbd55466"},{"version":"3.4.0-alpha06","sha1":"80f502b9a726ef7f627edd7ba7b4e8e1aae5bd5b"},{"version":"3.4.0-alpha07","sha1":"70a3c8033df63a513084ea5ea49014ae170221b7"},{"version":"3.4.0-alpha08","sha1":"149a83f8a985e845c736200064ec709989002356"},{"version":"3.4.0-alpha09","sha1":"2e9caee3c999506c6c1338305e9b2b3839ba244"},{"version":"3.4.0-alpha10","sha1":"f1ee831ae2a6a966da42665f9ab8d143ec2fa84a"},{"version":"3.4.0-beta01","sha1":"230a35ef2acf1cf10804f15e0302ff248e49f6f4"},{"version":"3.4.0-beta02","sha1":"a86733d3f1ffbe6b929af079a41e41cdd10e2105"},{"version":"3.4.0-beta03","sha1":"fed4c3a8dcf2ba40f168df02e350e6283dd1788a"},{"version":"3.4.0-beta04","sha1":"74918cdc8b6966b058d87a94eda31e6a636e2556"},{"version":"3.4.0-beta05","sha1":"92b3f429b122df662bf9b5cc68e5fa13771270d3"},{"version":"3.4.0-rc01","sha1":"6983e9daaceedbe7fdedcce4d845298a8a689956"},{"version":"3.4.0-rc02","sha1":"81a4b56613e316c8f8cff7f4400372b90d7b63ee"},{"version":"3.4.0-rc03","sha1":"2c545caf4f44975ced4c6a458f4c366a273fb511"},{"version":"3.4.0","sha1":"ac85b2c363dbcad0303df561c8c4f99a925c2878"},{"version":"3.4.1","sha1":"26d1d2ccf5b8580669d52cb21a9b7c846b78180b"},{"version":"3.4.2","sha1":"2c8085680612a322f4c0ae98d8454e190d0a057d"},{"version":"3.5.0-alpha01","sha1":"d1a9bebc050444625932321e7b85f16700a2957f"},{"version":"3.5.0-alpha02","sha1":"1663a994c856fffbbf460c43fac61b2f0be6d7bd"},{"version":"3.5.0-alpha03","sha1":"671935eee96ae088b074ea6001e5f32bafc08540"},{"version":"3.5.0-alpha04","sha1":"b846bef663e718b7afd13c54913f58a34a947eb5"},{"version":"3.5.0-alpha05","sha1":"f94d814a55e4310501f71d83b9219ddd9fc5bf2f"},{"version":"3.5.0-alpha06","sha1":"7ac609ce6ab3bc9b4526d0e29a53a70cdcddbdcc"},{"version":"3.5.0-alpha07","sha1":"739b898265fe39a1411db7ebc9fb0a53e4edd534"},{"version":"3.5.0-alpha08","sha1":"9ae90fee6ed40bb89e663747767707f397a99268"},{"version":"3.5.0-alpha09","sha1":"5fa2049815a77af25b0c20a126102693f3b40e8f"},{"version":"3.5.0-alpha10","sha1":"6e6e57ad54a36a85fa7f52fc0670c72fb327e9f8"},{"version":"3.5.0-alpha11","sha1":"a3988de359becb04216da95f634a6bcf60ff9c9d"},{"version":"3.5.0-alpha12","sha1":"27ace6b1dc6119058f9c24499c5a0a06e09733ea"},{"version":"3.5.0-alpha13","sha1":"443e171706b330669ac38bf037f0e3ce060cd0b8"},{"version":"3.5.0-beta01","sha1":"d5821ff67b3346124a472aa53ef1bd2370190363"},{"version":"3.5.0-beta02","sha1":"afc21aae5ac1f2893fd780768d5b1f719110aa3c"},{"version":"3.5.0-beta03","sha1":"31e5854466c9cbaa896b4121474546211232fee8"},{"version":"3.5.0-beta04","sha1":"bdde54bac380c9210104e959d944655dfc51d3c7"},{"version":"3.5.0-beta05","sha1":"f7226cb6a9285b948cb7d8c94304f016d81c62f4"},{"version":"3.6.0-alpha01","sha1":"2cd65c1a1e6eb369ca47137af0b8bff54aa323a1"},{"version":"3.6.0-alpha02","sha1":"bbf8a325ee39da6b873a4f0d903119f82a737e8e"},{"version":"3.6.0-alpha03","sha1":"bf31425c7077e9d8c82e8dd7622a8ae56168af6b"},{"version":"3.6.0-alpha04","sha1":"3c265470c787b6e8c8eee898b2f9a5ca1c17ce24"}]},{"package":"transform-api","versions":[{"version":"2.0.0-deprecated-use-gradle-api","sha1":"47f6c56527c42425742fa9b56ffd3fd80763fe03"}]},{"package":"apksig","versions":[{"version":"3.0.0-alpha1","sha1":"6ba4444b32634c55a6cf8013f98dff2c68225cbc"},{"version":"3.0.0-alpha2","sha1":"3fa88a9e49886fbf9e60b609a1a5d3ace95e3a57"},{"version":"3.0.0-alpha3","sha1":"bf92f780be5f15d26186bde5fcd7da1f8f7f4c49"},{"version":"3.0.0-alpha4","sha1":"ca264f79cc2e90800155c87c3a2cbf463eee21a4"},{"version":"3.0.0-alpha5","sha1":"ad7d35fa568a81c9e741aa6dfd673c504ecdc842"},{"version":"3.0.0-alpha6","sha1":"e55e38858ffbb6e4f94967143309dc2197d8bf95"},{"version":"3.0.0-alpha7","sha1":"23fcdc3a3a3ce953155f758e96629f9073846239"},{"version":"3.0.0-alpha8","sha1":"29697f840cfe3b85655a8114975aeea49c4fde5d"},{"version":"3.0.0-alpha9","sha1":"217f1a78acceaaa55160ef5a9cc3d2402b6a8cd"},{"version":"3.0.0-beta1","sha1":"66b7fca14564114af1e1fc3d33d20b983336cac5"},{"version":"3.0.0-beta2","sha1":"3c8e141cc8ee37d94e6a0df486590e568747b449"},{"version":"3.0.0-beta3","sha1":"30f7d736dc4f45d5b17f5eadba0b74ef00abef4e"},{"version":"3.0.0-beta4","sha1":"578e41bba43d045c2b230120172cbaa89bb4c4f0"},{"version":"3.0.0-beta5","sha1":"82c1325c93fbb12e19c2dbcaa36861049904c097"},{"version":"3.0.0-beta6","sha1":"19f30500fdd32d1dff1d40ed413bc79e77e140f4"},{"version":"3.0.0-beta7","sha1":"5a684dfc50aa1ff07c6d5a20495cbeda789c56d8"},{"version":"3.0.0-rc1","sha1":"933f24529f3be34717bd3e72a040cc15e7e30027"},{"version":"3.0.0-rc2","sha1":"cc5e6f56191d23de937fdfa1b9edd9dbbaf9be99"},{"version":"3.0.0","sha1":"21465a36db10551cfcfd8ad39bb496511f78354c"},{"version":"3.0.1","sha1":"535b9323a1bd47e3a9c860d11f4e11a80db13f2b"},{"version":"3.1.0-alpha01","sha1":"479fd24ff6712efba5d3decd7f89452a60070553"},{"version":"3.1.0-alpha02","sha1":"a820086cbdaddb856cdf446d85266e13828f7207"},{"version":"3.1.0-alpha03","sha1":"ae7b2abb21e6009a5cbad977515415a8d8a497ab"},{"version":"3.1.0-alpha04","sha1":"f3ca46ce81d4d43aa24fe7316568850216b881f2"},{"version":"3.1.0-alpha05","sha1":"c041e261e3e5732200553f3ac0c935182b9ca32"},{"version":"3.1.0-alpha06","sha1":"641a6540dedc153cb9a160c3d611ad1883876d61"},{"version":"3.1.0-alpha07","sha1":"198038b052150b0a708d32176aab4a42f959d262"},{"version":"3.1.0-alpha08","sha1":"da5a45e861eb3588ff025c7421602ff851844c00"},{"version":"3.1.0-alpha09","sha1":"712f567993d3e921ef640f218d0516c0224618d9"},{"version":"3.1.0-beta1","sha1":"2a3a78070de33ec95b6af3f141df5c01cc129c47"},{"version":"3.1.0-beta2","sha1":"da7f528f1f38d80cb5acc287c40a201d1bf8f2f2"},{"version":"3.1.0-beta3","sha1":"fec62a050b46826bcbe9d954e3ceae5c0ce7678f"},{"version":"3.1.0-beta4","sha1":"3ed19e7d6b46dd6341952b60f455f40720186566"},{"version":"3.1.0-rc01","sha1":"8ac69dcb56f2d1493a24113303a0b2d2e5558d6c"},{"version":"3.1.0-rc02","sha1":"ff1eae7db1afd25d262cfeab9d362f4130addaed"},{"version":"3.1.0-rc03","sha1":"dcae163354568ff4a7913ec167c15203645f69c"},{"version":"3.1.0","sha1":"94281878deacd451a0064dc69e866ad108ca158f"},{"version":"3.1.1","sha1":"241a5428079e16535cf17a3b8db4b0c8c175cd4f"},{"version":"3.1.2","sha1":"5af360dd30015a9a47c8ab0af0e6b05f64760edc"},{"version":"3.1.3","sha1":"72611f38098799f2eade760cd68468299264930b"},{"version":"3.1.4","sha1":"e0c4d0059e68e9f64606dfa01e066f86dd07c6cb"},{"version":"3.2.0-alpha01","sha1":"4dd790b7b27577802dbc0c2a17f3a1d0b842f8a3"},{"version":"3.2.0-alpha02","sha1":"d0d684be3fd5fc09256f1b28aa043841f775233e"},{"version":"3.2.0-alpha03","sha1":"ddf1b46e619336105ca74a31fa65af1f7e8481e4"},{"version":"3.2.0-alpha04","sha1":"cd5da51ce15e5c09e3ab1fd8f7f624f0db8866f0"},{"version":"3.2.0-alpha05","sha1":"5bf2eabf8baffb46fbc1cb18bdf3a71c6c5766e"},{"version":"3.2.0-alpha06","sha1":"dc33819fe42463a5615e184cf8210425f6bc8541"},{"version":"3.2.0-alpha07","sha1":"e8d49de4e529a0284fd629ed90f3d4fe322ddd21"},{"version":"3.2.0-alpha08","sha1":"ff3cc65bfcee751688c836018cd2aa2205952c5f"},{"version":"3.2.0-alpha09","sha1":"765515317ae142bd5ec8adf6623a97801ab6e346"},{"version":"3.2.0-alpha10","sha1":"a6720685637e24880f018072ecee615fe7dacc5b"},{"version":"3.2.0-alpha11","sha1":"a782d2a9d6db17253fbca2b71011f3ecf3ddb18f"},{"version":"3.2.0-alpha12","sha1":"e8514657886275b68f1a270baccfd548e8ccabd3"},{"version":"3.2.0-alpha13","sha1":"a096aef28f4442b7be8b0797c70b7eea62ab35b3"},{"version":"3.2.0-alpha14","sha1":"45ffbd6df0f8ec2ea3a585c4f775d7ac306f9607"},{"version":"3.2.0-alpha15","sha1":"3643cbef722e13e61aaab1191a712f9e872f84d1"},{"version":"3.2.0-alpha16","sha1":"2e500cf35c8c976187aadaefa82eecef8afdf799"},{"version":"3.2.0-alpha17","sha1":"d7553b1aa22a9b297100d0ffe89358eff6739f49"},{"version":"3.2.0-alpha18","sha1":"4b5f80f88547b8238e1e1ddbfdf255dd56b2137e"},{"version":"3.2.0-beta01","sha1":"ea8a38eb637e46fb4f5994aa28036fb10b0767db"},{"version":"3.2.0-beta02","sha1":"a1ff1a07e0f728b163544df33a8ef5fb9625920c"},{"version":"3.2.0-beta03","sha1":"842b33d8db547325e9ffbaac4f8aea3c2c5f2715"},{"version":"3.2.0-beta04","sha1":"fd3ac95bca1cedb89d4eda23b51881dd6e736164"},{"version":"3.2.0-beta05","sha1":"2a5b41d5e82d0d16ff988869390804043eb73155"},{"version":"3.2.0-rc01","sha1":"c66cb841aef64c454168b538276562e15b340287"},{"version":"3.2.0-rc02","sha1":"655a449f8868e2f6ec44398244dda7c27b6756af"},{"version":"3.2.0-rc03","sha1":"dce2b4370c6670fbfcfd2c2e3b4b2e86b9c20ea0"},{"version":"3.2.0","sha1":"b7dba62005ccb2995c2e31a642677ece0fe1ba1b"},{"version":"3.2.1","sha1":"ad6d930fceb350753d645f4bf66b3eb4d2ad566"},{"version":"3.3.0-alpha01","sha1":"df0664e62fcbbd9e87899094da762e09152c26f9"},{"version":"3.3.0-alpha02","sha1":"d2b31959579c68832d53a1fe865fab77a3238317"},{"version":"3.3.0-alpha03","sha1":"8d549523bcb69a3f1aaacdd8fb191ff645039070"},{"version":"3.3.0-alpha04","sha1":"ed4bdd69605a9f4cf75cd93afdc12bf772c586e4"},{"version":"3.3.0-alpha05","sha1":"c31da382e15db0a156749e003b58987cbf7bf4e2"},{"version":"3.3.0-alpha06","sha1":"5709bccdb16a525f415f1c3c50d5151cf115a8bd"},{"version":"3.3.0-alpha07","sha1":"a0e013b0e17f0b30ff5cc66ea6463471006df5cc"},{"version":"3.3.0-alpha08","sha1":"bb7e4c83db49dc0f66019e662722fca3b4d6eab5"},{"version":"3.3.0-alpha09","sha1":"900817af66d3c05e1f8f11a0fca03bcf3e507b5c"},{"version":"3.3.0-alpha10","sha1":"e2ffd279303393be82f6c97fbf2eeb5fbd609391"},{"version":"3.3.0-alpha11","sha1":"999208f5576bb51e43ac80b74ec6102b336e276a"},{"version":"3.3.0-alpha12","sha1":"8f2c4f2dd17eea515b78bbaf154a572fb5d13b50"},{"version":"3.3.0-alpha13","sha1":"6765225101fbe6e65f80220796ddf57cdc544290"},{"version":"3.3.0-beta01","sha1":"597bf6c7a04b5d21fe177059d1df84f9bbbfecf6"},{"version":"3.3.0-beta02","sha1":"969443fb88394a08546daebb581c49b4c56054ec"},{"version":"3.3.0-beta03","sha1":"1b3c2b1dabb4cdeb23d8ad8ec9839afc7c1eb1e1"},{"version":"3.3.0-beta04","sha1":"aa2ee985cbacec96408efff8d35853d3728a48db"},{"version":"3.3.0-rc01","sha1":"92a11da92316286d16d1fb9cc4e997d7502ff7ec"},{"version":"3.3.0-rc02","sha1":"72096348ddc67ecc8b7d0db85f84033b45758889"},{"version":"3.3.0-rc03","sha1":"d49497e08c8a2b310045fbad755de933fb49e36d"},{"version":"3.3.0","sha1":"1ca00cbc7282cbb34b1274fe3c3578ce3861367b"},{"version":"3.3.1","sha1":"5dc32667e573a18b05119685ac3f2b8c0f17182d"},{"version":"3.3.2","sha1":"74ee675eb81dbabc5265eb023b2514c8e45b686"},{"version":"3.4.0-alpha01","sha1":"b95d8feb8bc094a8fe07bd03e76dae5ecf3087be"},{"version":"3.4.0-alpha02","sha1":"736b2460b19ecebcdb66c031ef08170f4ec3a913"},{"version":"3.4.0-alpha03","sha1":"7b9727c135384da6192d29250b5da19f44fec055"},{"version":"3.4.0-alpha04","sha1":"f13fb4bc893325a63f6b554851ed2e9dcd133ab8"},{"version":"3.4.0-alpha05","sha1":"c6ebbc2016a22c2de528f195ed92f69e396b9c18"},{"version":"3.4.0-alpha06","sha1":"e687aa39ee3df9524027c850af064818fa213d67"},{"version":"3.4.0-alpha07","sha1":"d606e252af31cfb2271983db72713b375ceab4bf"},{"version":"3.4.0-alpha08","sha1":"e1b9839216a46cc01fdaab7b428344c9f06c432b"},{"version":"3.4.0-alpha09","sha1":"860567422fdc4c048408b773edc2321a072e4215"},{"version":"3.4.0-alpha10","sha1":"fdf27f13d941f3180381b8234f6fc4a95a742d9c"},{"version":"3.4.0-beta01","sha1":"4f55e49198018a33214d807235338a7322a665b8"},{"version":"3.4.0-beta02","sha1":"65dec8f4a713a752bb10250e43d2b686b6cb4ccb"},{"version":"3.4.0-beta03","sha1":"66fe1fe663e1f9520de46d99679ff9b538c5545c"},{"version":"3.4.0-beta04","sha1":"a86feb2bafeb24ae6f78519cbae36d1c75a7407d"},{"version":"3.4.0-beta05","sha1":"f6b7717b83f48a49e035aa372ae3c0475fb00870"},{"version":"3.4.0-rc01","sha1":"e64cabec28112bb60012a4e89a42f211e39dc826"},{"version":"3.4.0-rc02","sha1":"b5902abfd1f8c1abd141002b806780ebf0b23c0d"},{"version":"3.4.0-rc03","sha1":"1a7b375a658d73fbabad24a7f352d44ecfc43285"},{"version":"3.4.0","sha1":"5f67d13c63f838b3add79f01e7bb7ffe0e1c2e66"},{"version":"3.4.1","sha1":"5a8ac41195adf44ed36028965574bbc9dd1e06c0"},{"version":"3.4.2","sha1":"1f0eeb02e487c484ef0d3b1855b5ca41b21f4b38"},{"version":"3.5.0-alpha01","sha1":"adba69dff46b94f19b8a3c03c58e25710e4592c1"},{"version":"3.5.0-alpha02","sha1":"31a497316a7ac4b2b51dcf97b27cc4fc8060109a"},{"version":"3.5.0-alpha03","sha1":"2884bb9d8612aa926296e15a886aff63fe6a4417"},{"version":"3.5.0-alpha04","sha1":"9d3a05212739ab4ef13d3a8c3ec07a7e8d432493"},{"version":"3.5.0-alpha05","sha1":"ed498ef10b6381ad0163f0f7e93e29e92af18b0c"},{"version":"3.5.0-alpha06","sha1":"7ac3fca1ee451e612106601e95906ea8cc9234ba"},{"version":"3.5.0-alpha07","sha1":"fab68accceb6a838bae2fb9f887238ca23a0f9e8"},{"version":"3.5.0-alpha08","sha1":"bdbfc532b1862aedc1f389523e4540d3c227170d"},{"version":"3.5.0-alpha09","sha1":"e399dda93386972e690098de84447eace3f94a92"},{"version":"3.5.0-alpha10","sha1":"32f47a6d5e54da61aebb10582631b2b1a9257915"},{"version":"3.5.0-alpha11","sha1":"1def9b1728c5274ed909e8b376aaf16b44f1a346"},{"version":"3.5.0-alpha12","sha1":"b1734890f7d14716fcba65d61e6f9ace493cee8e"},{"version":"3.5.0-alpha13","sha1":"8c309a9cada00377b560ef099477141d6477fddf"},{"version":"3.5.0-beta01","sha1":"1744f96c4a0a03b68a540c079b6fec01bf32812e"},{"version":"3.5.0-beta02","sha1":"56bbabc08ee786aa1f59e703ebbaf45b1f8cd414"},{"version":"3.5.0-beta03","sha1":"d75d083cd5448adcd9688357af0766829413ba82"},{"version":"3.5.0-beta04","sha1":"c7ba13e0410d46c2aaf33330c79735fb6c4230b5"},{"version":"3.5.0-beta05","sha1":"6fecc9f5398727c7723df4fc98cb93095646bf44"},{"version":"3.6.0-alpha01","sha1":"d56475a4c56bb771dffce36d3c0d09e52d819be7"},{"version":"3.6.0-alpha02","sha1":"4675cf9b211c17417944796a7c0599a3ac73d99a"},{"version":"3.6.0-alpha03","sha1":"28feeb26cd94a74c4b446b2943f679662ec53803"},{"version":"3.6.0-alpha04","sha1":"993db3a03e8c38f42dc02d6a9b58af3b441df82b"}]},{"package":"builder-test-api","versions":[{"version":"3.0.0-alpha1","sha1":"55a7ce17c54c4c83d04022bd35e2003266b59900"},{"version":"3.0.0-alpha2","sha1":"5b553a6bc6e1f70e1f29d6b765577055f3d4f7f6"},{"version":"3.0.0-alpha3","sha1":"a25c0749e2ea89f038816d24468d24aed1d1f63f"},{"version":"3.0.0-alpha4","sha1":"59802215aa85fa09147f3ef610a653b4ada0f49"},{"version":"3.0.0-alpha5","sha1":"d9304e6528bb3d0f01dedbdf859007eff1354ec8"},{"version":"3.0.0-alpha6","sha1":"7f8afb62739267c9137fd93c3254c6c93359421c"},{"version":"3.0.0-alpha7","sha1":"902e47f3584fa6c735f6d85d31efca69e2fdff85"},{"version":"3.0.0-alpha8","sha1":"b27884b08e97110155f7fd62c87a0724b9e74c61"},{"version":"3.0.0-alpha9","sha1":"9731b9f4408f5c1f9931b16436875ea74949c9f3"},{"version":"3.0.0-beta1","sha1":"976a470efe5ffbdb83c62971ddaee42815222fff"},{"version":"3.0.0-beta2","sha1":"49d2819350499dbad54facb61f9bca4a6824d62c"},{"version":"3.0.0-beta3","sha1":"8efa1d747e7c86f8d7581065a4496782c034d572"},{"version":"3.0.0-beta4","sha1":"a452fdccc593f243725d45ecd927db9e6976838a"},{"version":"3.0.0-beta5","sha1":"7489579fac2dded0dceaa9c317858964a33330c0"},{"version":"3.0.0-beta6","sha1":"b727cfd129200743c8669a5e1e4bc4074f9fb834"},{"version":"3.0.0-beta7","sha1":"acf1c99e215da49e044d0cd3c0fbd2d01dc0dd08"},{"version":"3.0.0-rc1","sha1":"711fe034a1087d3958ca652c826cae1c9740cbcd"},{"version":"3.0.0-rc2","sha1":"4e4f60ebae0126239cc76f48f5906e2d71902fbc"},{"version":"3.0.0","sha1":"9bb354af21d8754ecc9c1c07f80bd85e3a76695f"},{"version":"3.0.1","sha1":"5f084a2888a7f638c4c89c3278760345b1fb4c8"},{"version":"3.1.0-alpha01","sha1":"e2c2781b774f4f04e06a560c809a9cefe30e723d"},{"version":"3.1.0-alpha02","sha1":"2cf613a415ec2695112e5b31bb8c1b2428f5ee4a"},{"version":"3.1.0-alpha03","sha1":"5de492adfba794f12833c415ee757e992eb0abaa"},{"version":"3.1.0-alpha04","sha1":"2cadd25aff025e46632c6c56c0d29e7485eefd32"},{"version":"3.1.0-alpha05","sha1":"d5ec2bf92f882897e79ec8b53bc86d7560891cf2"},{"version":"3.1.0-alpha06","sha1":"1b1cce90fb8c4a8aef5d6f305000a633edb4a0ac"},{"version":"3.1.0-alpha07","sha1":"b91c9ecab5d4baf3459491df6a7a5dc65c50620e"},{"version":"3.1.0-alpha08","sha1":"8d2038e3e2a55ea5d7a4445d8914a30b1ab12f4"},{"version":"3.1.0-alpha09","sha1":"1efa0a19ccf5b9de2d85ad98931cf963df7f4586"},{"version":"3.1.0-beta1","sha1":"9ef2451a355f85bcdfb0dffc71fa371d65e2ce15"},{"version":"3.1.0-beta2","sha1":"d3f964c806f88858eeff4a1be10cede280998521"},{"version":"3.1.0-beta3","sha1":"1fcbd8a6b35aae30f7c8be0d84e447601c09b734"},{"version":"3.1.0-beta4","sha1":"a64ed2b705bf0e2f7ebf64b694e91f20de432099"},{"version":"3.1.0-rc01","sha1":"930e102682d5ae654783d6c3eee66a313c7daa84"},{"version":"3.1.0-rc02","sha1":"5cf2adbb1b42ec0d4363137a7485ddb4b8dbd0eb"},{"version":"3.1.0-rc03","sha1":"d4f2599d586d8cc2707a0af378425ae85d22fa66"},{"version":"3.1.0","sha1":"86320e296592f2795a8454eb94eaa0c0379184b9"},{"version":"3.1.1","sha1":"f38fa7cf59ebb790f2ee82b4158b32563823e5c7"},{"version":"3.1.2","sha1":"ffb00b786822df6538377a90df9f2d11c022efc3"},{"version":"3.1.3","sha1":"9248d673a7d82e6656d373884f24e22163d16b28"},{"version":"3.1.4","sha1":"e48338f64f1c8fbef8329eb6d899fb6378fca604"},{"version":"3.2.0-alpha01","sha1":"25e4c7a3fbd46edf4d400e9ced70b79fc02ff36b"},{"version":"3.2.0-alpha02","sha1":"3ec096188c048045e659f85ce22b5597178a0e81"},{"version":"3.2.0-alpha03","sha1":"8a97b2aa87742c56938c8111bf91d37ba516ef02"},{"version":"3.2.0-alpha04","sha1":"8316f78b310395daf4f8b200a37a59ce22640505"},{"version":"3.2.0-alpha05","sha1":"6b52f99633e692afbe1bcad70384abdeed95b7bf"},{"version":"3.2.0-alpha06","sha1":"c249fc9da11ca219aecb943945d2f49a5c2d34bd"},{"version":"3.2.0-alpha07","sha1":"a94257c468d50c9bcc7be567299490ebd5e2094a"},{"version":"3.2.0-alpha08","sha1":"b18b48e6eaafb78bdf47dc4b51616dca87929e81"},{"version":"3.2.0-alpha09","sha1":"80eac5c67d38283542dcb83bf6339cfcbdbcdf7d"},{"version":"3.2.0-alpha10","sha1":"6fadd9341865876a86d5430e54891986b6bfdcb9"},{"version":"3.2.0-alpha11","sha1":"d9d742f9b5183a9837714abff4aef90e8669775c"},{"version":"3.2.0-alpha12","sha1":"97a1d8a2fbf8b7bc66c94888a00f0d90b19aad49"},{"version":"3.2.0-alpha13","sha1":"8a71f72952f4c7d8673c6bc342cc2eee701efd8"},{"version":"3.2.0-alpha14","sha1":"b18d5559765b085b37690965f02ddf9e7e76745f"},{"version":"3.2.0-alpha15","sha1":"db3a94dba275f1f5bf12c505d4e7f0ac44b01c1"},{"version":"3.2.0-alpha16","sha1":"adda591ec5bf397d1e199012d66377523bf6c88c"},{"version":"3.2.0-alpha17","sha1":"9c29002f4c3dc2c594646bbc2b1f721cebabbcb0"},{"version":"3.2.0-alpha18","sha1":"ed5297f6ca235915b78b4d049852937c87177d59"},{"version":"3.2.0-beta01","sha1":"7c09cca204886a6cb2aace5c6df3174b510c031a"},{"version":"3.2.0-beta02","sha1":"6c04c5ee38f012c5d59068a003be7e484a619e87"},{"version":"3.2.0-beta03","sha1":"20d3ae8ea2f3c4dbe8140cd4b0ff8e526eff5cdc"},{"version":"3.2.0-beta04","sha1":"5ab0f5384b926177d37301fc7f00597feed61a80"},{"version":"3.2.0-beta05","sha1":"29817f0257578fe2f6edd97210fbd04dbcc1abbd"},{"version":"3.2.0-rc01","sha1":"ca76e1cd92100a4038224c559eaf31a1986dd5ea"},{"version":"3.2.0-rc02","sha1":"d27123e26c43bc0ee3a98c102e9029b6119c1f75"},{"version":"3.2.0-rc03","sha1":"8b2511561f90e39116eac94124a197f778eecb1"},{"version":"3.2.0","sha1":"d317895ce3ae7c835bff91e1128283bc7e6f1c6a"},{"version":"3.2.1","sha1":"9c0b9848b34d79d71f911ebfaf6e0d606e7a3e6"},{"version":"3.3.0-alpha01","sha1":"7b6c74a8c07dd45dac5ae093fb5d5b47b9a0b815"},{"version":"3.3.0-alpha02","sha1":"5d6beb9aeb87f0cdc7bcad217ed5eff2625909a5"},{"version":"3.3.0-alpha03","sha1":"89a98350e5554d3c070681d96dada4004d89f3df"},{"version":"3.3.0-alpha04","sha1":"d5954a0aa771c5e38072470a24acd7402023da20"},{"version":"3.3.0-alpha05","sha1":"1791790257047137d2ec2ad72d8d8d2b7cc1c867"},{"version":"3.3.0-alpha06","sha1":"63739e7bc074fe9da219da01e620c19ca3beb03d"},{"version":"3.3.0-alpha07","sha1":"d3c9bdfb63e6d68ee91086cf4872c733883eaf17"},{"version":"3.3.0-alpha08","sha1":"47e90cba12318676b423d7f245711dd3cc346c7c"},{"version":"3.3.0-alpha09","sha1":"df5ce4b97f26e80ef163b7227fea31c86dffe13f"},{"version":"3.3.0-alpha10","sha1":"35c0e67a2d85721cafd8d52fdd77822a875c3a67"},{"version":"3.3.0-alpha11","sha1":"78dadb86425e8f99d9bcf17cdda1702b822dca25"},{"version":"3.3.0-alpha12","sha1":"c34e1b2642b72e8de32dd679c2d1950c8619aa63"},{"version":"3.3.0-alpha13","sha1":"f4b5e9e0ba1cfa354f6c09dfdc1e3f6f42ebb227"},{"version":"3.3.0-beta01","sha1":"7a7ea945a3b1e58b9ddf2fd08c931501397188c3"},{"version":"3.3.0-beta02","sha1":"105090ca4fd91628b393aaf41e877c1b27383aa5"},{"version":"3.3.0-beta03","sha1":"60d7eb75d0b5f909210c375f2982f9e95d97c2f4"},{"version":"3.3.0-beta04","sha1":"b36e4ac3b02370fdd06dafe05597e3e1ac29b54c"},{"version":"3.3.0-rc01","sha1":"b509ba540669316ea6a115eb6fb476f79163af71"},{"version":"3.3.0-rc02","sha1":"10bbc64bd834b1a85cbd9280a24269eb54d0671c"},{"version":"3.3.0-rc03","sha1":"789a7ef35870a13cfc2f381c36b0d4284deb270d"},{"version":"3.3.0","sha1":"cac14c63920d52fbb2cba8cb21bbe5cde7b745ed"},{"version":"3.3.1","sha1":"c1a1ce91c0f34c43a52c54c1a2f6fdbaafadd3da"},{"version":"3.3.2","sha1":"d5d259c68f5b87ff18656eaccd5f48b6612ab537"},{"version":"3.4.0-alpha01","sha1":"342760561e466095adaea2f2eb40c82f7ddf6020"},{"version":"3.4.0-alpha02","sha1":"96bd1e7c013a22a4c85c330d7bf3b73386272ff0"},{"version":"3.4.0-alpha03","sha1":"a8e7653666b324077ebb775bc054d20b0075e2dd"},{"version":"3.4.0-alpha04","sha1":"36a82aeeccaa4951dab3f7b2d478aeafecfc1e4e"},{"version":"3.4.0-alpha05","sha1":"74b0f3b4bc37d29b4143f3f728312ed7b0cf39e4"},{"version":"3.4.0-alpha06","sha1":"c2a045fe0492090ff9f08b5145b93048867f8333"},{"version":"3.4.0-alpha07","sha1":"d228201d866ccb36cad20b5f45a35a4439c17a07"},{"version":"3.4.0-alpha08","sha1":"f094444008016d7588c2baba38a3b9a5e8e03ab"},{"version":"3.4.0-alpha09","sha1":"43990e6e3c6db79459fef1c8026adf0f3be6437"},{"version":"3.4.0-alpha10","sha1":"221924ce908c9232ba9f57f802d66c822646d96b"},{"version":"3.4.0-beta01","sha1":"bc7e0b53eb1cda9d169aa3d1e706f9e661ccd5e5"},{"version":"3.4.0-beta02","sha1":"bba40459342788864f49c35366efdf539e450dac"},{"version":"3.4.0-beta03","sha1":"1ce9b37aa2a28ac85e72f8d020534dc8367c6753"},{"version":"3.4.0-beta04","sha1":"22327433f0bd3b36b91de5e02f0e7ac8a3277b33"},{"version":"3.4.0-beta05","sha1":"94749085cd850b5aeac3974222d0ed180d3f5a62"},{"version":"3.4.0-rc01","sha1":"e9817a3f420e311f8ff2971dbe2da8c1e5c73fcc"},{"version":"3.4.0-rc02","sha1":"c84bd6fdc447595bf56c464c24610b01dd4e20cd"},{"version":"3.4.0-rc03","sha1":"613e2f65f7c215063de1d7604192be012d94e16d"},{"version":"3.4.0","sha1":"c7b44da938640cb249dcc719ede09c2e9896711d"},{"version":"3.4.1","sha1":"54d24a8d74dfdc087c40854fef6adcdad059285e"},{"version":"3.4.2","sha1":"73ff274f705acc35ce48c8a5b288a80333ba4d0c"},{"version":"3.5.0-alpha01","sha1":"ca2bba0ae48389955c45ba2803b6c22c923c6b4f"},{"version":"3.5.0-alpha02","sha1":"969346079c93956bceceaad5e8d4095738297fac"},{"version":"3.5.0-alpha03","sha1":"e152563ca1fdeb777192ad5c0546bedda61533b2"},{"version":"3.5.0-alpha04","sha1":"88ba46da3a975be74c3d16a7bc88045134b86367"},{"version":"3.5.0-alpha05","sha1":"e595c048b91ba79b0bf317b829701554f306567c"},{"version":"3.5.0-alpha06","sha1":"68fe13cf6a494fa83fa7b52715038cdd3c2af824"},{"version":"3.5.0-alpha07","sha1":"f840a63771168ebb92ca1bde1bf34a5688a5de35"},{"version":"3.5.0-alpha08","sha1":"b3173142bc4a84c6870d97f15dd08c6233e17572"},{"version":"3.5.0-alpha09","sha1":"3ee5d78e614ee1f113dd41712ce47e95e468a2f5"},{"version":"3.5.0-alpha10","sha1":"12b1e073716a947add4116b3470199c79d1e300d"},{"version":"3.5.0-alpha11","sha1":"83063572cd9ad34fd3b3a096926b1bb9f51aaf00"},{"version":"3.5.0-alpha12","sha1":"de598fa14528c4cd193dd089cc7d3a0a0553d9cf"},{"version":"3.5.0-alpha13","sha1":"e71788d7bcc4597f9cb562bb52cafc062d3dec80"},{"version":"3.5.0-beta01","sha1":"7f1fb9ac6f62dcf0cad13354e69444547cab2245"},{"version":"3.5.0-beta02","sha1":"d49e3bb1345bd491ae1a64cb2118f63f6c99ffb6"},{"version":"3.5.0-beta03","sha1":"d9b8439e57145957c2948f2dce065cbd45cd6102"},{"version":"3.5.0-beta04","sha1":"a7922e48a20e873c2d918ac72fd97926c4276ccf"},{"version":"3.5.0-beta05","sha1":"ef16ab6d099eb052c0187dd4dd4da8fdd9e0eccf"},{"version":"3.6.0-alpha01","sha1":"c30e626a5de38054bed6a1cd34dd1d7b158dd4c9"},{"version":"3.6.0-alpha02","sha1":"528394a021eac20bc2e6bdfa9a7b3e47eb0875f4"},{"version":"3.6.0-alpha03","sha1":"b946512efd7359b491e99fb1898ae0dde86c2491"},{"version":"3.6.0-alpha04","sha1":"98ba4f83e1d7ce0d0637b8d3b8dd238192ebbe2"}]},{"package":"gradle","versions":[{"version":"3.0.0-alpha1","sha1":"92504ed1501fe40939a6753fc864e5c6840712c4"},{"version":"3.0.0-alpha2","sha1":"3462af20a11569220edbfdafdb71091817838b1a"},{"version":"3.0.0-alpha3","sha1":"d0fc16652d9d8899accc58c2e51f6f94a84f6bcc"},{"version":"3.0.0-alpha4","sha1":"c579bd98f359743b5aea7a10ea0acf42ee0847b4"},{"version":"3.0.0-alpha5","sha1":"3201cd549c08bb1d5136d65bde1b6b98d3db329a"},{"version":"3.0.0-alpha6","sha1":"6346aa5792a7efd48e21b6e6a1142b3d702bf277"},{"version":"3.0.0-alpha7","sha1":"68f15510a015a7256cb3f1b0ba50c9497bc47fa9"},{"version":"3.0.0-alpha8","sha1":"7c10166b82208b3882f8d0bce12aa6b1618affb2"},{"version":"3.0.0-alpha9","sha1":"f070263c36d06b94e33ce5520e24e0adea0427be"},{"version":"3.0.0-beta1","sha1":"ca63e08d3d7f1fd670a10680d3e037d796394dd8"},{"version":"3.0.0-beta2","sha1":"7b2e5485e69a43b255ef4d353f3beb04df780783"},{"version":"3.0.0-beta3","sha1":"1411a3aab7c88c87e79d0d574c5da6ec688dfa62"},{"version":"3.0.0-beta4","sha1":"4e143b46128be5ccb30d92f662c9f763b72bf4de"},{"version":"3.0.0-beta5","sha1":"6c64d0f154b08ee89451bf3509df2a3d3396d3e3"},{"version":"3.0.0-beta6","sha1":"948b8c05b40b5f607a49d58e7c66a95faa61134f"},{"version":"3.0.0-beta7","sha1":"4670a91b3a553376bb1cbf7b21dc7001b8047598"},{"version":"3.0.0-rc1","sha1":"5901062e30b2ce08165017df5ec196ac7cdf8540"},{"version":"3.0.0-rc2","sha1":"178a58dc51f04c7481fc38498aa6eac752c1f1f8"},{"version":"3.0.0","sha1":"2356ee8e98b68c53dafc28898e7034080e5c91aa"},{"version":"3.0.1","sha1":"cfa107d4db0c11c11f445a11e768adf61c72ce06"},{"version":"3.1.0-alpha01","sha1":"7a2480019ceec50cb2058a14b88eb43687b5d982"},{"version":"3.1.0-alpha02","sha1":"e42c7098f6d5ed5f0c7b59d368f2eea608d630c1"},{"version":"3.1.0-alpha03","sha1":"87e51e862c961072de704af53f014bef127162d0"},{"version":"3.1.0-alpha04","sha1":"d208d38cfb0267d41984cdc786b4973bc51b23d7"},{"version":"3.1.0-alpha05","sha1":"12f0566d8c5733651848d36e983a0cd6b40b7b26"},{"version":"3.1.0-alpha06","sha1":"fa4607efb453798e626774977a2a88a15548c4c0"},{"version":"3.1.0-alpha07","sha1":"129d70dfedde5c0fcd4743a1c8b6ce42f87f075a"},{"version":"3.1.0-alpha08","sha1":"9ed8eff7ddce098d7f8e04080fec703b9801003"},{"version":"3.1.0-alpha09","sha1":"6bfed22fd678d0a7287334aee80642a3be41554e"},{"version":"3.1.0-beta1","sha1":"2428e0188a04af9d29bdd51553015fc2a93a312d"},{"version":"3.1.0-beta2","sha1":"2e4c6e87adb3e54a1aeb158bbbada4d275b5bdcc"},{"version":"3.1.0-beta3","sha1":"945e9da439cb05745fa29f2a71015c7ba3ce1f2a"},{"version":"3.1.0-beta4","sha1":"5057a5be665d17954d5887d041eac2880a266fe3"},{"version":"3.1.0-rc01","sha1":"32a0392458bdceb47cf0b3b84237c4dc7d9595e6"},{"version":"3.1.0-rc02","sha1":"5e9a1d63f4a7f2d788f61636202725c0a93ceafe"},{"version":"3.1.0-rc03","sha1":"8a28ea479986d2de5ef6f4cae467a5d2a27ac043"},{"version":"3.1.0","sha1":"7d9e6c6fdcdb06722c1af6f5b4a70994c232cf5d"},{"version":"3.1.1","sha1":"47b7b5cc3c2f8514d71c4d77aa32fb17b25fab56"},{"version":"3.1.2","sha1":"1608fa49add4d13366db7844998c6e59711f7e2a"},{"version":"3.1.3","sha1":"1b1884e0c0083ecd83400210659252eacee151b4"},{"version":"3.1.4","sha1":"8f9a726f69c0c8fa3f447566717a21e6b394ed9"},{"version":"3.2.0-alpha01","sha1":"8b3791ff14c8174af41fc9b1754403b45f3d2cdc"},{"version":"3.2.0-alpha02","sha1":"4c4152a29d81101313f9593add6f9025c682405"},{"version":"3.2.0-alpha03","sha1":"c11ad6da4a740315735606cf833fc02cfafceea8"},{"version":"3.2.0-alpha04","sha1":"2d5ef319b81b934ccfb20172b9861678ef5f1911"},{"version":"3.2.0-alpha05","sha1":"b7717c50917af169e72ef3395f3cd6340243905f"},{"version":"3.2.0-alpha06","sha1":"871532962b274e9470718e7234654c894edca71b"},{"version":"3.2.0-alpha07","sha1":"fbdcc628a4cd7ef97e566b74803fea1f0e1c76d3"},{"version":"3.2.0-alpha08","sha1":"a1bcfbf8ac56f544b59b4920162378bf3ade77c2"},{"version":"3.2.0-alpha09","sha1":"aae03bc06fd655254a4656fbc8258cee29747e16"},{"version":"3.2.0-alpha10","sha1":"b72d34d7196c5fc722e4b56bcadd49fd2b9ee266"},{"version":"3.2.0-alpha11","sha1":"c3d611d36edcfe7239a336539340cbb6cbbf629d"},{"version":"3.2.0-alpha12","sha1":"3c6e86968e1a89e43ff944f331fd330150d7ccd8"},{"version":"3.2.0-alpha13","sha1":"2abfeb29d5d7553bfe67d2fe30898ed09f1e0f73"},{"version":"3.2.0-alpha14","sha1":"e248cc8793e1a138cf35b5818b449b578c93f059"},{"version":"3.2.0-alpha15","sha1":"9980398c24ec96c50dfa6bcf82cf150575efd614"},{"version":"3.2.0-alpha16","sha1":"6def7de8a0b87f682b184eb35e46689407bec8a3"},{"version":"3.2.0-alpha17","sha1":"4074776778013ad2c825b2c22633afb8003e63d5"},{"version":"3.2.0-alpha18","sha1":"70cdee024d8dcea2d1e34cbfea9e1dcd9cc2d3ae"},{"version":"3.2.0-beta01","sha1":"8b95ccdd699df56e11d2e16ce53d2df37041d1e7"},{"version":"3.2.0-beta02","sha1":"a702288dda6b49f3c4e75c789e5e417c41f545cd"},{"version":"3.2.0-beta03","sha1":"7e674edd68ae8802118363dabd392bd836df2ee7"},{"version":"3.2.0-beta04","sha1":"8d083cd1ab5d131541019b81d6827a99edda5aff"},{"version":"3.2.0-beta05","sha1":"4c605c02cbbd52c6a18f01a018646fa1a1f97c5"},{"version":"3.2.0-rc01","sha1":"3d4d2f992c5d444981446c0959d2dbf125376b8c"},{"version":"3.2.0-rc02","sha1":"9f426179fe036a54b1c76134bebd27c7ce8a1e92"},{"version":"3.2.0-rc03","sha1":"515e3db6f6837aea715f268c341e1c6c262b618c"},{"version":"3.2.0","sha1":"1851dd6a2badb1a66e5fcafc311073d7ad0b3183"},{"version":"3.2.1","sha1":"1ce0d907aa7805e19f553807b9bbdc9bb9841dbf"},{"version":"3.3.0-alpha01","sha1":"1aa412f055e30db65416ff1709cb37a1e20587a5"},{"version":"3.3.0-alpha02","sha1":"1d0df954fc285ec8e3fc284b1b62e2319f6ffee4"},{"version":"3.3.0-alpha03","sha1":"59f634d6bd1a339120c39223c49fe0c5cf1e020b"},{"version":"3.3.0-alpha04","sha1":"70f842e2644c9b9bee54660ac039f8867f70682e"},{"version":"3.3.0-alpha05","sha1":"c4424b3fbd855c33792f483491aa7c77e0d2e049"},{"version":"3.3.0-alpha06","sha1":"5c1d3d6246fef8f8969e1c7875b09652fe70d5de"},{"version":"3.3.0-alpha07","sha1":"c6502b46def7b78b42e3dbffe310e3614b0cf31e"},{"version":"3.3.0-alpha08","sha1":"79ef2fc6b076f9f4d78c48349a65633c3101b153"},{"version":"3.3.0-alpha09","sha1":"372cf8cea5c01ceafd1b194e62947ad494e0e4c8"},{"version":"3.3.0-alpha10","sha1":"d0bdec9671b66c41b4c451a25fb5307cd2f81d30"},{"version":"3.3.0-alpha11","sha1":"6f4bfeddbce65f2b37108dcb36dcbdeb574ab694"},{"version":"3.3.0-alpha12","sha1":"56f4aa77912ebd21439eca5efed21b667d495497"},{"version":"3.3.0-alpha13","sha1":"65a8f5558e7e9671138af4f682de7f9ca6cffb8e"},{"version":"3.3.0-beta01","sha1":"b49fb4a4df2a9b5cae794de10c3625d0615bc6e1"},{"version":"3.3.0-beta02","sha1":"c4a11413add9c76b37a3d2bc6ff514c4e997ab40"},{"version":"3.3.0-beta03","sha1":"52d1984123302f7c2c95b0e56da586484fd28f4c"},{"version":"3.3.0-beta04","sha1":"8e88e7a361c8f21967492fbf272c541ea79a54c1"},{"version":"3.3.0-rc01","sha1":"afd6ed7260f6e2d59c9bb7457d06d505c1582495"},{"version":"3.3.0-rc02","sha1":"1794972bb9f7e306ce46c8c58f11451b0b3812a9"},{"version":"3.3.0-rc03","sha1":"2757c59c10d6cae1cecbc025d500c313fbf9ff96"},{"version":"3.3.0","sha1":"bcb8b8acc91b5a79346027bdfdd1fded03942088"},{"version":"3.3.1","sha1":"7d642f14aa35443dd7aeecd0600c17da37d3bccc"},{"version":"3.3.2","sha1":"362925d001bd44692fce774f957cfe80522fdbd0"},{"version":"3.4.0-alpha01","sha1":"8008757a7bc1ea705613097f9e701a5a844d64cf"},{"version":"3.4.0-alpha02","sha1":"28173adeafc3fe1e4a5215bc283a1c546dc10dd2"},{"version":"3.4.0-alpha03","sha1":"86f19c67d50c306bb33b44691b3586d85ea9fa01"},{"version":"3.4.0-alpha04","sha1":"afd943b9a744305250d1cb6bc60cbf6be12b925e"},{"version":"3.4.0-alpha05","sha1":"6fb592d28cab8845ab665170d627eb83ecbeb04d"},{"version":"3.4.0-alpha06","sha1":"b8161f177428bfb53f9c43a472960b453518fce6"},{"version":"3.4.0-alpha07","sha1":"91d7fa6ee3f30f2a4a35108ac8292fe005727f0e"},{"version":"3.4.0-alpha08","sha1":"1d918356245c115c7ae5875045de8836c36df705"},{"version":"3.4.0-alpha09","sha1":"3be36ca3e5fb8b49180778b5e9d233cd9ca79e2d"},{"version":"3.4.0-alpha10","sha1":"256780a067ad000bcd255ec557f031e2bfc096dd"},{"version":"3.4.0-beta01","sha1":"d15f92f6f43048baf7804ad583d5588a4f1c2149"},{"version":"3.4.0-beta02","sha1":"e03f9dfd2d115f11282f9a93d9c7c8c601d0080a"},{"version":"3.4.0-beta03","sha1":"e5c1d3af9e863fece24873249b9715e76384e428"},{"version":"3.4.0-beta04","sha1":"8f58667785e7c4bf1c3014a9113f4d31003ac7ef"},{"version":"3.4.0-beta05","sha1":"566eef64f605e0fc3a764436dc81630870df5e3f"},{"version":"3.4.0-rc01","sha1":"f175c9e6f651bd61a333a699d926c2293e1dcbb2"},{"version":"3.4.0-rc02","sha1":"b6eedc980486bdb68a16c3b47a088ec4cd56e40b"},{"version":"3.4.0-rc03","sha1":"116917ee58ca22c675feb404873ee28527acdaf8"},{"version":"3.4.0","sha1":"16af40a504b8f8ff572c8c2bbf4ace3dccd2f5b"},{"version":"3.4.1","sha1":"195bd39d36b255d333d6493dcac0d542258d2a3d"},{"version":"3.4.2","sha1":"24af6d1b2890cd52b99f4650ab523a3c3a09f71d"},{"version":"3.5.0-alpha01","sha1":"62f3723cb00eff683c3bc0f796400c4c0b79174e"},{"version":"3.5.0-alpha02","sha1":"e5116fa5083f88fe4d5229036c5f6cbc45aafd9b"},{"version":"3.5.0-alpha03","sha1":"dd20dc22e5eedb30f1af5b5b883795375b1f5532"},{"version":"3.5.0-alpha04","sha1":"67e62187c0816cb4a9ef9eb7b768721f106cb038"},{"version":"3.5.0-alpha05","sha1":"7110398c1cc2f23ad75655d374b51645ac992d00"},{"version":"3.5.0-alpha06","sha1":"75c8223346e5de53d369d8f4e241a0171b83dd7a"},{"version":"3.5.0-alpha07","sha1":"e3bbed4e10762e1de1bc13c05911be7aee7c2694"},{"version":"3.5.0-alpha08","sha1":"b9320a746c125005356306378527541bad8c1d67"},{"version":"3.5.0-alpha09","sha1":"fc8a2315d0adc9c7f1a2d2dc581492e7423038ca"},{"version":"3.5.0-alpha10","sha1":"fe3c8feec0e3b349a5ef5a4c532ae4d499bb3eab"},{"version":"3.5.0-alpha11","sha1":"ccf9de8810c7d112ed2c435161f9888b8f79d92d"},{"version":"3.5.0-alpha12","sha1":"5d29a168bc28a6ebfc0aa4e9c9ffb285034e2fd3"},{"version":"3.5.0-alpha13","sha1":"cdca6a2aa8003aaeb7d9f4211ffa46586f68bda6"},{"version":"3.5.0-beta01","sha1":"d07b3c79f72e3c1228e4a4055e0d7e69715be5b0"},{"version":"3.5.0-beta02","sha1":"e84069f40a30ffdd1ff47e45253133f28ab533b7"},{"version":"3.5.0-beta03","sha1":"1d7aa68b696448393fd31abf12622c5163ee70d5"},{"version":"3.5.0-beta04","sha1":"32cc3b7ccb037349baad9c7ef266f99002c5a6a9"},{"version":"3.5.0-beta05","sha1":"d6a0e21c1960028f272565563c9e4180b8969918"},{"version":"3.6.0-alpha01","sha1":"aee88b512dac034d288ca69467ffa7e7ee5b1dc2"},{"version":"3.6.0-alpha02","sha1":"60b1a194de2b67fd74953a06f50600e4fdd552bb"},{"version":"3.6.0-alpha03","sha1":"dbbd92bf9f2b9ba70610d941b300ef29bf15a6f1"},{"version":"3.6.0-alpha04","sha1":"dd0bea6d5e278fca1c0b529c203d614ef69008e5"}]},{"package":"builder","versions":[{"version":"3.0.0-alpha1","sha1":"832c3cabdf0c4d52ec8a3e8aeb711e5ce524c887"},{"version":"3.0.0-alpha2","sha1":"dc232100c45446b8104a738d8fd7292e0fc6d937"},{"version":"3.0.0-alpha3","sha1":"c1b09a08c94880fd4f030ec220adf148b0a6c98d"},{"version":"3.0.0-alpha4","sha1":"c7afbb32e2acd23b9e39fb6d411cc1b3d59e6555"},{"version":"3.0.0-alpha5","sha1":"cd46c42d2924d99b2b73cbc218f1c7d8086c1b52"},{"version":"3.0.0-alpha6","sha1":"6729eb007a59356a5541133edebd84afebf179aa"},{"version":"3.0.0-alpha7","sha1":"c721f71bf0052da032f089c7d4a7eecb6268f6bb"},{"version":"3.0.0-alpha8","sha1":"87ec895afaa6300a7b045670e58cf7d23dc3332c"},{"version":"3.0.0-alpha9","sha1":"d2fb263fbe418e04a43ac53dd033375373b9392c"},{"version":"3.0.0-beta1","sha1":"e70b4f1cbe0df1831712d57b1aec14684b1ab236"},{"version":"3.0.0-beta2","sha1":"8c8c376abebf511ae779d833c68578a827edfd2e"},{"version":"3.0.0-beta3","sha1":"e1c9cffaccb6e14b40c1446a714d5be291f14e3b"},{"version":"3.0.0-beta4","sha1":"d5e17553817d17c54f11dc7b2678a2d92b3bba2a"},{"version":"3.0.0-beta5","sha1":"6f8a5e291d6b10ea68ed58138ad55c2dcec948b0"},{"version":"3.0.0-beta6","sha1":"9f65c0c21a62818825fc72c16a82f3f4019fbdb"},{"version":"3.0.0-beta7","sha1":"b763d5adb4e6f1cef80e1361681df8443731005d"},{"version":"3.0.0-rc1","sha1":"bcbe214e1076d915c3a1c03bd8d863b3e0e4b6bb"},{"version":"3.0.0-rc2","sha1":"a294121a26d1a914974b2a729cebf02fe1df9dad"},{"version":"3.0.0","sha1":"36884960f350cb29f1c2c93107f4fa27f4e7444e"},{"version":"3.0.1","sha1":"1f896967507729bb35ac727c03d3b9306fe87b7d"},{"version":"3.1.0-alpha01","sha1":"c62e6d77b001c83cd0f43a31556b0ec1126ae955"},{"version":"3.1.0-alpha02","sha1":"1686923185c61f224c6952c33996cde22323dfda"},{"version":"3.1.0-alpha03","sha1":"dac8a27dc532d6cead66a3776f48a6087570f3e4"},{"version":"3.1.0-alpha04","sha1":"b116889bf54824717eda48eb401177e26334a9a8"},{"version":"3.1.0-alpha05","sha1":"1b394e1ec90a28d0ff59e8333d0e3868a555162"},{"version":"3.1.0-alpha06","sha1":"e1837133e028a612c3d3c0b4a43dc398d1b80439"},{"version":"3.1.0-alpha07","sha1":"297c3f3f9562fe33ad1bcef2a36e2171a4a941a1"},{"version":"3.1.0-alpha08","sha1":"fc36c387a2bf238a61f18a016b22b3d63cf64c43"},{"version":"3.1.0-alpha09","sha1":"1b3b36fcfa0e90e16c47cf113fb0c6b858b8ae98"},{"version":"3.1.0-beta1","sha1":"677c38fcccde8a22f5a72c59bf385e61338e7a10"},{"version":"3.1.0-beta2","sha1":"e3c73c9ad46df901a1e0a5e025981208105b8185"},{"version":"3.1.0-beta3","sha1":"6af8ff63924da8733b717656f81f55cbaf986940"},{"version":"3.1.0-beta4","sha1":"c8e8816767a1fe4b1253c2d5753d29f1549ecabe"},{"version":"3.1.0-rc01","sha1":"d625ff3dbb5d2af9b813c1200e8843fb90298f62"},{"version":"3.1.0-rc02","sha1":"483308d13f498f743ffa6445610c4795edaf49a5"},{"version":"3.1.0-rc03","sha1":"7fbb9c29fee2e64d404fffa897003c12248498a4"},{"version":"3.1.0","sha1":"6f2e330c71bafd68c16e294fb1653dfae53678b4"},{"version":"3.1.1","sha1":"4c0680e6e92557fe9c57f6150606c8f2080f14a9"},{"version":"3.1.2","sha1":"133b1f665104f0ebf01f71b61e4794385d7b5f1b"},{"version":"3.1.3","sha1":"564a1b75eb6a4fed551e17b8d791e495a092fcb9"},{"version":"3.1.4","sha1":"afbcd4b7002c61fe898b1b4c50ed9e62386125d8"},{"version":"3.2.0-alpha01","sha1":"a69d88d93e48791bacca5c22f213f8802bc44363"},{"version":"3.2.0-alpha02","sha1":"d60f419742d333fe66f4ffa7354d89da303edfe8"},{"version":"3.2.0-alpha03","sha1":"a79cc1b075acc72a3ee36ae4a1561fe072688d1a"},{"version":"3.2.0-alpha04","sha1":"5b74914ed9de1d0b7d95fb086fb5d671d4a5416b"},{"version":"3.2.0-alpha05","sha1":"a1ad8f60ce3f861ebfd25b3b03d22e35b54bfc0"},{"version":"3.2.0-alpha06","sha1":"a127bc26ca21470c986cbeeb5fe79b7122bf3413"},{"version":"3.2.0-alpha07","sha1":"ed16955ac76ae3513ba4d4e7027d348441a2ddc8"},{"version":"3.2.0-alpha08","sha1":"6fd023eb73720e2a823c64d8c5f53c41ba1eb985"},{"version":"3.2.0-alpha09","sha1":"a337cabfdc31a65f70036c4ce6f261eec267ceab"},{"version":"3.2.0-alpha10","sha1":"3c98c86dc889428509fc3ee8e0faeec50a0d9fcb"},{"version":"3.2.0-alpha11","sha1":"753ba52c8db5d9c9fa812f70cf932a8f844767d7"},{"version":"3.2.0-alpha12","sha1":"2b5a00e37ede74a75a9e944070f4b0e414b4e20d"},{"version":"3.2.0-alpha13","sha1":"5c5722898ac15d4edad81f5b4c64c9be0987af08"},{"version":"3.2.0-alpha14","sha1":"531c2a1159ff322e4770664fb6dbf4a063b1da31"},{"version":"3.2.0-alpha15","sha1":"7ec55d1a0097648f91d18ba6ff6fa3f4d300b102"},{"version":"3.2.0-alpha16","sha1":"8d9b21b4ff4b1d3c730f2132357aaa797bb473a6"},{"version":"3.2.0-alpha17","sha1":"147d3a54733dae91b6b392a663a211a9627578fb"},{"version":"3.2.0-alpha18","sha1":"245cdefdbba051d7aa0ab20498f96c10ed4d28e1"},{"version":"3.2.0-beta01","sha1":"3f0d095204d6ef749e568308ac6dcabc505a41da"},{"version":"3.2.0-beta02","sha1":"49a83ff7b39b91f035daec26528f4c3486e25237"},{"version":"3.2.0-beta03","sha1":"76c14fe613a5f9ae1599e7ab04b18507a99f0ef"},{"version":"3.2.0-beta04","sha1":"11ebb9f5cb9ec8d78e71a2fe3fb1e71815db324"},{"version":"3.2.0-beta05","sha1":"e717f591940287cc8c4c63d2f5a3d41715b0964c"},{"version":"3.2.0-rc01","sha1":"6a3ae917a225a7ba68e0f464987e20b24c01ff84"},{"version":"3.2.0-rc02","sha1":"7d3dbebe2e4f1c7d1297c3b0a80308ebb0fe2102"},{"version":"3.2.0-rc03","sha1":"c1b9e383a5c35fcac739d3ea172cb3443eeba678"},{"version":"3.2.0","sha1":"4b1a6361fe804bf24270535f86ea734f3a6b4e46"},{"version":"3.2.1","sha1":"1303a2feb969ac0896e7c83c1f5a0fd2496b71bc"},{"version":"3.3.0-alpha01","sha1":"707928fa3036609bece0543375bb6e1d7553eeab"},{"version":"3.3.0-alpha02","sha1":"59b4c39b3a8eed5cea46dd2f4e3ed53f3ce48271"},{"version":"3.3.0-alpha03","sha1":"4552d61cbb6edc822f89b8dd5e90b68853cb9aa8"},{"version":"3.3.0-alpha04","sha1":"5f409d8cf0d8efc1d3de9824a00cc2ac3fa0f640"},{"version":"3.3.0-alpha05","sha1":"42afc6e8e701ae69b2c2745b24c4bb469b59f092"},{"version":"3.3.0-alpha06","sha1":"25349756cbb60f5623675cd8eb41701d6cf7752a"},{"version":"3.3.0-alpha07","sha1":"b5476a6b93cb4d7b1aa80f3e74e746bc898c9d8d"},{"version":"3.3.0-alpha08","sha1":"56995c4c1347c52e7d0b32a81c422727aa4d18e"},{"version":"3.3.0-alpha09","sha1":"27b54b943ed3fc7d027aa9da268218fd2419a820"},{"version":"3.3.0-alpha10","sha1":"b4b6843c280062d52fa0e4fda66bb5fd6f39549"},{"version":"3.3.0-alpha11","sha1":"13de4263a0ba9ce9640ba41b4351322978ae702"},{"version":"3.3.0-alpha12","sha1":"7fe1b21b121e92cd4e6d6f66116326c5aa826645"},{"version":"3.3.0-alpha13","sha1":"ab9b749e11ff10f1ba0309c125f5f78c05e687a2"},{"version":"3.3.0-beta01","sha1":"2a56fae4ef16cae4c4d085ee3cb7093e604f8e55"},{"version":"3.3.0-beta02","sha1":"6a9d3d8b0a2aacb4801ab97fdd38511ace0b1fc7"},{"version":"3.3.0-beta03","sha1":"f8368a47416831c8fd54223f68f6418a36bd7328"},{"version":"3.3.0-beta04","sha1":"417b473a8101f0c3aa5b7b3150c399c1a2bc76b3"},{"version":"3.3.0-rc01","sha1":"a0d34c095ebc7c0ba6cf3b656d946507ce69333"},{"version":"3.3.0-rc02","sha1":"e8d6344cb4e7b8eaee65ea7a425869e13c4aafaa"},{"version":"3.3.0-rc03","sha1":"3f325d9674b896e40f784ab7c43aef99c60bd1dc"},{"version":"3.3.0","sha1":"de1cc306a8a198f59d56cb9a909d9a4f516116e7"},{"version":"3.3.1","sha1":"e4d2ad1ef694fca700b3ab1f4df4b79313d17c98"},{"version":"3.3.2","sha1":"3339f921baa1e0dd4656effb03146db9db227ee"},{"version":"3.4.0-alpha01","sha1":"36e4db053d13e8f4c436a81f543029c5d928a790"},{"version":"3.4.0-alpha02","sha1":"a0db53a3e7cc748364418cfd4e4f1e7a09660a5a"},{"version":"3.4.0-alpha03","sha1":"db515491407d7953eae5b6b386438a5d44c328b2"},{"version":"3.4.0-alpha04","sha1":"80c23b07aac71c04a990b9aca6eaf014a123881b"},{"version":"3.4.0-alpha05","sha1":"58484d3a8026aec88dc28f7241f7acbddeb2d346"},{"version":"3.4.0-alpha06","sha1":"ada80c796e6727d075b488bfdb42ef5cb2444b91"},{"version":"3.4.0-alpha07","sha1":"ef3be19670dc384e9f155b1ac84cdb5683a71d46"},{"version":"3.4.0-alpha08","sha1":"b36894e3c1a28640460cec789a71580d2d95268f"},{"version":"3.4.0-alpha09","sha1":"ce51bdc99655fd39700e41d106625426270b7916"},{"version":"3.4.0-alpha10","sha1":"f1ac2a2687ab11d78bce132ab167320e2eed347f"},{"version":"3.4.0-beta01","sha1":"989adf698d571a81f169563df6e597a77ae6c463"},{"version":"3.4.0-beta02","sha1":"f3ee36a204b4eca5f560157eb29bb26f547dd11c"},{"version":"3.4.0-beta03","sha1":"1adfdcde06e4c61bf36e39ea634878d8b6112698"},{"version":"3.4.0-beta04","sha1":"e1f16622e05cc176278b7b62a2e6a1be1fcefc7c"},{"version":"3.4.0-beta05","sha1":"3f0985ee6f1c271b5ccd11df45cf84102900f473"},{"version":"3.4.0-rc01","sha1":"afae97f8bd3407b5f3115b2fae490339603f5914"},{"version":"3.4.0-rc02","sha1":"4d48298ce97557d402c8000b5b6817bdf434dfc9"},{"version":"3.4.0-rc03","sha1":"96eef6d35235dc57bb47f15ed2f9f873ed823823"},{"version":"3.4.0","sha1":"b237d03672bae54a1013deab3bbd936d78f07e79"},{"version":"3.4.1","sha1":"5fd3cae600dcbda8adaf1791c0e844e585ac0d3d"},{"version":"3.4.2","sha1":"3dc402cea753c84151a01a8d49c2c68a731a57db"},{"version":"3.5.0-alpha01","sha1":"b19c358a6c4d88ab595ec0077a9374ed2a876ce2"},{"version":"3.5.0-alpha02","sha1":"14b846e5e56e3eded475a3fd1de077b5d78b829d"},{"version":"3.5.0-alpha03","sha1":"f447e87b968d3b041bfa22152dba82d00d723098"},{"version":"3.5.0-alpha04","sha1":"a9070c368f0759a836b9bf44ced2952e0e7dcb31"},{"version":"3.5.0-alpha05","sha1":"ce239bd6b20126d6a9908993582aa6bc78b3a3c9"},{"version":"3.5.0-alpha06","sha1":"ede3cba6775102941ac33ac5eb9c82e1f8b13d5a"},{"version":"3.5.0-alpha07","sha1":"5f8fc717ee3e4f5f9ded20c9cf61bc7904ff01e5"},{"version":"3.5.0-alpha08","sha1":"7547af8f9d5070d8a0a971a2339f9a2290b38171"},{"version":"3.5.0-alpha09","sha1":"5c5e2b2f60b92bb992b2f881291e427938b54041"},{"version":"3.5.0-alpha10","sha1":"c2baeba289958afba65d4f9d31b6c830c5314940"},{"version":"3.5.0-alpha11","sha1":"953a5ac9fdda53232081b9de9ded553bba5ab042"},{"version":"3.5.0-alpha12","sha1":"60f6d1f215dd4b45c91d50a32fa0d1a4da690dee"},{"version":"3.5.0-alpha13","sha1":"d877214809217560228813238fcf199279cae707"},{"version":"3.5.0-beta01","sha1":"66da9250ccfee6757e891c5874d5e8b77c9374b4"},{"version":"3.5.0-beta02","sha1":"ef109bcf2aa555ac8f47b5c39069bb1bbebfa3d2"},{"version":"3.5.0-beta03","sha1":"11264030cf69ac0e2643b150b146383209dc7f3f"},{"version":"3.5.0-beta04","sha1":"4dcec86d7a719831dfacffbdfcde521b77b88b35"},{"version":"3.5.0-beta05","sha1":"41ef0bcec47cab4cbbfa225bb24fbd48ea52d94c"},{"version":"3.6.0-alpha01","sha1":"2b79a6b4393e2e809e0f939a63d1ef7b9c09f6cf"},{"version":"3.6.0-alpha02","sha1":"daf37677114202909edbfcc0e983e00950e72af2"},{"version":"3.6.0-alpha03","sha1":"14693a68a813c99b4ce18487d8bd12b89e2f9420"},{"version":"3.6.0-alpha04","sha1":"89d90dcfe796b77e02882a3cc8fa3f59da9c0170"}]},{"package":"builder-model","versions":[{"version":"3.0.0-alpha1","sha1":"b8e630f5c2889ff3e1d7438f8c4b2d8de11b9953"},{"version":"3.0.0-alpha2","sha1":"b1a608392c2752bd59e9940d7aaa8194caee55e2"},{"version":"3.0.0-alpha3","sha1":"787f2a02873aa27304cf20097a7be753401650c2"},{"version":"3.0.0-alpha4","sha1":"92206668813743758effbc9988d1bc169be07df3"},{"version":"3.0.0-alpha5","sha1":"3f79bc2a9ea29a301dd34651afaf63302f1f4982"},{"version":"3.0.0-alpha6","sha1":"32f008bdf4c777fb0cea8605bb353ee57d8ebaf7"},{"version":"3.0.0-alpha7","sha1":"247891c2837c844db54e36654a6574f356125c04"},{"version":"3.0.0-alpha8","sha1":"4d965eff6a51d4ff39714fc1477346c940efaa5a"},{"version":"3.0.0-alpha9","sha1":"fef59c2bab9dc673b65fac7d328131d7d79cfc47"},{"version":"3.0.0-beta1","sha1":"b321170778bcc53bea36bcf4c8b6a9c887b9899"},{"version":"3.0.0-beta2","sha1":"1fb72a558740d51934a54d68dfb1660109a8fadf"},{"version":"3.0.0-beta3","sha1":"5adfcbe4dfa0318f91e277b33e12dacef7de3ef4"},{"version":"3.0.0-beta4","sha1":"3f043091ff041da58242ce83b33e27cf14bc08c"},{"version":"3.0.0-beta5","sha1":"f05b7fc3bfb71812deaf23520e70b76cf98b62e6"},{"version":"3.0.0-beta6","sha1":"eeb7ebf0c9f3233a1bd4aa24fea74f9a9aa2621"},{"version":"3.0.0-beta7","sha1":"a2a042b8ffcac93bef58bca39cc6c2cee4ffdf2e"},{"version":"3.0.0-rc1","sha1":"169448cbdb358f27b248fd3f831027db2a0e8f47"},{"version":"3.0.0-rc2","sha1":"4583b06e0abaa07f8cd071ffafac6a8d097b02f6"},{"version":"3.0.0","sha1":"a86b254415fded5297e1d849fa1884dfdf62ff42"},{"version":"3.0.1","sha1":"e9de7e34339a8eb90fda30ac6bf580d5f2c4f282"},{"version":"3.1.0-alpha01","sha1":"75558aeba2619d43f4435194f720579b80d6dd01"},{"version":"3.1.0-alpha02","sha1":"ee9a559f8fb67727296c71228d04a00d239939af"},{"version":"3.1.0-alpha03","sha1":"b4f998507c67d04e7f8cc0c21f2463c1c6dd9c19"},{"version":"3.1.0-alpha04","sha1":"c15edc2e26fd3f039a47b9fb523a6fc6203c2de8"},{"version":"3.1.0-alpha05","sha1":"afd9cc31a8f6c66e6ab7f7f5613563ba4d54d388"},{"version":"3.1.0-alpha06","sha1":"4bfd0bde4990420b1f8e060a97521b2e8fc6fd09"},{"version":"3.1.0-alpha07","sha1":"131d69193e528264cc5a43337325737f17458317"},{"version":"3.1.0-alpha08","sha1":"b23c1931794a427f88c6113832851b079a4711e2"},{"version":"3.1.0-alpha09","sha1":"7ddfdb0dbab78f3095bfc1ad472a8fd36fa450a9"},{"version":"3.1.0-beta1","sha1":"85e427c3eebe11c943cf6aa39769e434c6eb1748"},{"version":"3.1.0-beta2","sha1":"5da22126c07cebbec92e6620577835031776dedb"},{"version":"3.1.0-beta3","sha1":"48177525e240e005ed1cc48b3fabbc784dff9b3c"},{"version":"3.1.0-beta4","sha1":"e0909f1445ce15b0a418f99ba24aa31384ee013e"},{"version":"3.1.0-rc01","sha1":"c583cc7ebd9a10900eae08cc9d6cdfb0cc5a049a"},{"version":"3.1.0-rc02","sha1":"fd2e4640ac7a37bd5a4b60317c6ad406994932b4"},{"version":"3.1.0-rc03","sha1":"3d7ee54520333775d230e89ec7c929f294cd90af"},{"version":"3.1.0","sha1":"7103d1d71aa7185f1d377f6098d50ffb6494dafe"},{"version":"3.1.1","sha1":"ce6c9f8342caa5428b9640c7d2f2dc8161dda674"},{"version":"3.1.2","sha1":"4504b655fa8fe72302020ca9a2387f3f23fbfb57"},{"version":"3.1.3","sha1":"b87a8f87d1d512fdd7beac9f0745e478d80540ae"},{"version":"3.1.4","sha1":"f026c31b8228a84b62bb2fe0ac0544143e9ab27d"},{"version":"3.2.0-alpha01","sha1":"dae3493ecf80cd725c2b0a08b419022b972660f2"},{"version":"3.2.0-alpha02","sha1":"7f45493f42a9ac6c92022a8458c3875259f167e3"},{"version":"3.2.0-alpha03","sha1":"cd463c1e2623f0594d6159a9704932106158f3e1"},{"version":"3.2.0-alpha04","sha1":"775df72c2ff5040b7e57a1bdba2d8d1ea83d2379"},{"version":"3.2.0-alpha05","sha1":"3a8144b2240863c2758fbb34bba3c854e116d0d8"},{"version":"3.2.0-alpha06","sha1":"42de38b72bfd22ef1430567caff42e7ae016b9e8"},{"version":"3.2.0-alpha07","sha1":"8e2d1c64e491b802f8e0f15b58ac647936831b1f"},{"version":"3.2.0-alpha08","sha1":"fec3600af0d602b27b1a6c5c32a5bc9cb3be371"},{"version":"3.2.0-alpha09","sha1":"b756c82ab895a67f5c3c1341e0aa0f34d74208de"},{"version":"3.2.0-alpha10","sha1":"c64fcdfe249491dc078ee5e0b21784c46cfbfb37"},{"version":"3.2.0-alpha11","sha1":"87bf3a5b1a86e5e95f69ca8f9d414aa219883a1b"},{"version":"3.2.0-alpha12","sha1":"6b0e2f5a76ccb8d8fdab1c0a85a83a44bdd51e88"},{"version":"3.2.0-alpha13","sha1":"80b3c470f62e234ab410b74335e64dad9a0ad4d1"},{"version":"3.2.0-alpha14","sha1":"8e38adbea7507604194c0805989c5057e3c7657c"},{"version":"3.2.0-alpha15","sha1":"46cc4f5ce81bcc9570025c05233dc4cc1e8c38ae"},{"version":"3.2.0-alpha16","sha1":"6b6c3bb65e0ed3d8fc2a9ef9a6ae7c039df7c0d8"},{"version":"3.2.0-alpha17","sha1":"d1257c9621fbcdf7f73f1e1a2f0735c8823f9c7a"},{"version":"3.2.0-alpha18","sha1":"26c8c9b5767d927fd5cae62ca0a9dda5b233b02d"},{"version":"3.2.0-beta01","sha1":"4de58db59417b86818c4c38534d07d075c0711ec"},{"version":"3.2.0-beta02","sha1":"179f7f7b69fc5f51c4d041995e92e61090bdffa0"},{"version":"3.2.0-beta03","sha1":"4b6c982f3674ea602c2639adf39d74855af36c71"},{"version":"3.2.0-beta04","sha1":"7815799401315c4ec582e869df170b2c3eed5183"},{"version":"3.2.0-beta05","sha1":"901953dadca2a040d560cc3a4f95bb469a372dca"},{"version":"3.2.0-rc01","sha1":"36caa746c2f3e412f27723565205ade21459e8cc"},{"version":"3.2.0-rc02","sha1":"a553e3f290375512e5410b7810fa3426eea3013"},{"version":"3.2.0-rc03","sha1":"2e36332fab664901abaf0ac9c7c1170e6d113c46"},{"version":"3.2.0","sha1":"80c620e25a9bdc1ae3e41a948f372f72b14a3fbd"},{"version":"3.2.1","sha1":"3984b3e65c34b5d278e434270b63802451ec3e9d"},{"version":"3.3.0-alpha01","sha1":"bfce64dadd2d03c9e72cfcb28a81c723f5142909"},{"version":"3.3.0-alpha02","sha1":"a3d108597343454d5e25c4a67488028c6898d251"},{"version":"3.3.0-alpha03","sha1":"a4dffa6dfbac4ab70e13e876f9489c250c9b32ff"},{"version":"3.3.0-alpha04","sha1":"6616098deb2943fc396f4f412c1a8fef718d938b"},{"version":"3.3.0-alpha05","sha1":"8e2e621455c391ae3559a3b77c151fbd6f8e5ee8"},{"version":"3.3.0-alpha06","sha1":"bb2afc7fe9c1b69cc1281199babc4f238e3a4996"},{"version":"3.3.0-alpha07","sha1":"310b5381c5d04b32fc885a3465fd8b5c522afc9f"},{"version":"3.3.0-alpha08","sha1":"73c066c15a66e48cf74d18e60ea6ffca7d2e2117"},{"version":"3.3.0-alpha09","sha1":"5392d159b22e70814cca35f456fc6969ec058d64"},{"version":"3.3.0-alpha10","sha1":"5034347184df50d6d435c0be981eaadba89e96f1"},{"version":"3.3.0-alpha11","sha1":"cb4b5867fe405efffdfbbda14e1ab314fdbc51f0"},{"version":"3.3.0-alpha12","sha1":"42879d45a23cf72e977566a38dd8905d93ff6365"},{"version":"3.3.0-alpha13","sha1":"83734bfeb8f601104689ac912f61b480cb233601"},{"version":"3.3.0-beta01","sha1":"58658939de5dd7d168c5cea6038b35a9af6a6719"},{"version":"3.3.0-beta02","sha1":"7aac74eb3ad125eed438d59e79fd4362371dd2f6"},{"version":"3.3.0-beta03","sha1":"b432a58ba0a42acdb7ede88e55411d5740a5c157"},{"version":"3.3.0-beta04","sha1":"fc3220c09bfb7b95faf65da67aea929c7c7cca5d"},{"version":"3.3.0-rc01","sha1":"c556e1806a8da709247537034319550fe05593d4"},{"version":"3.3.0-rc02","sha1":"fc9af33dd997efb155efc28c08918482c198d457"},{"version":"3.3.0-rc03","sha1":"87c03b7b9c5142e8e8bd5e557c80def586859332"},{"version":"3.3.0","sha1":"968b90fbb48c3bf7e12bcef875d9b417255fd200"},{"version":"3.3.1","sha1":"2c47cf0451da2eaad484ccd2d580a8cfd23fa064"},{"version":"3.3.2","sha1":"25c4f11baf4b92e56bf2dff4f14b0538eab2fdaa"},{"version":"3.4.0-alpha01","sha1":"a6d78b86534a0cb386a7caba2ece1b5903594d5c"},{"version":"3.4.0-alpha02","sha1":"927f6e70fae9583aa3ed8f638e1b8bfa7b71fb84"},{"version":"3.4.0-alpha03","sha1":"a5cfae39264c31e95fe1b2d0eca40e0a89bb7598"},{"version":"3.4.0-alpha04","sha1":"fcb5c1d07ba713a199a7e8b496c9c8f8f102e35f"},{"version":"3.4.0-alpha05","sha1":"acd5188d16c784510499c0e0ff554280b79c8553"},{"version":"3.4.0-alpha06","sha1":"4f9cf179c689487dce15f210467df8365a511d17"},{"version":"3.4.0-alpha07","sha1":"b3e9a912377b51fee1e586575c18e806640a19dd"},{"version":"3.4.0-alpha08","sha1":"83a1baa2a7dbcadc8b720e64d6b180b338af32be"},{"version":"3.4.0-alpha09","sha1":"5327b518251fe76c54ed43357f81aff5b9abd2b2"},{"version":"3.4.0-alpha10","sha1":"3ef25d969ab9dea6f377b46ede2502d56a1591a8"},{"version":"3.4.0-beta01","sha1":"a598ee96880fae2543c8e278b29f7add1b0cac74"},{"version":"3.4.0-beta02","sha1":"9b0228a826f84bfe892c9ae71eeb250fee29df00"},{"version":"3.4.0-beta03","sha1":"3faf3116a6121a90fcd595b3d01372e5dabf21de"},{"version":"3.4.0-beta04","sha1":"3b650d5830c4bf0bcda6690c50362b3f2a43659"},{"version":"3.4.0-beta05","sha1":"5315f658235db65e5e0aa7289131504e64f2ddf8"},{"version":"3.4.0-rc01","sha1":"cbfe9c8a70e67016232800e92d9a87c2994d7bab"},{"version":"3.4.0-rc02","sha1":"fea40572dbf507f314be33b44ef2cb33e797279b"},{"version":"3.4.0-rc03","sha1":"416e55e999d950fe714614fa34329a0ccebd9b8e"},{"version":"3.4.0","sha1":"768154ff0029313694e6b7d1576ef79dfe7e2b7e"},{"version":"3.4.1","sha1":"5354cb1d4bad046574ce6e7c6400a5f5bbaf4653"},{"version":"3.4.2","sha1":"ede3f12170e6c416e84c26b4e60e2c85784e233f"},{"version":"3.5.0-alpha01","sha1":"b081f06639405a766e4f7cd02c2371ade5f00781"},{"version":"3.5.0-alpha02","sha1":"446274429bea59bf38a578f63a92dade713854a7"},{"version":"3.5.0-alpha03","sha1":"435bea9f890e0b61725cb112e8892b3bccf8b12e"},{"version":"3.5.0-alpha04","sha1":"671f660e328b0b3ee08ce31bbd13869315fdfb76"},{"version":"3.5.0-alpha05","sha1":"1509e7dae25f1d2843c6dc8004119f45473dd6be"},{"version":"3.5.0-alpha06","sha1":"1e1573b8f20ee006be1d6760550a2d646ef307fa"},{"version":"3.5.0-alpha07","sha1":"15bcf1040984d08987b2489292d7ce58c83cbdb2"},{"version":"3.5.0-alpha08","sha1":"4801d4d6eab55b3d498ffdbab63504215ebd21f5"},{"version":"3.5.0-alpha09","sha1":"3b72174daad5d0dc64a1336391ce785ee05a7c78"},{"version":"3.5.0-alpha10","sha1":"4420292927572853562b6a4a209c35036ac48287"},{"version":"3.5.0-alpha11","sha1":"40c42f149b964894b88a7d52464b9e30bbcb3139"},{"version":"3.5.0-alpha12","sha1":"cc489ad4e2a27bf5e077e2e6350ee71599e55bae"},{"version":"3.5.0-alpha13","sha1":"396b5f7930dba8b20bc80e2b653bc2c06fab887a"},{"version":"3.5.0-beta01","sha1":"93b918dc557652f3f41ca9d8e7af3901b3861357"},{"version":"3.5.0-beta02","sha1":"233808aeae99b9e56e5fd64ae46891839d33eb79"},{"version":"3.5.0-beta03","sha1":"aa416a63674289cd6c555caac5e0408c857a0273"},{"version":"3.5.0-beta04","sha1":"2481db482edcfcf412e5b8b995fc0a43edef4734"},{"version":"3.5.0-beta05","sha1":"3ed149b3d26efa0fa6f8169b99856696f5c7c68d"},{"version":"3.6.0-alpha01","sha1":"4c0774ac9a09952cd781c325d818079eaaa6230b"},{"version":"3.6.0-alpha02","sha1":"fcdb2c7a00527def78c0f07c97b7b1f2f7260700"},{"version":"3.6.0-alpha03","sha1":"b5fd197dd5b14db8004c8910a7856f94cbd4d8a7"},{"version":"3.6.0-alpha04","sha1":"7ca1a99e53a1f84c4631f4a022ea9f4274200787"}]},{"package":"gradle-core","versions":[{"version":"3.0.0-alpha1","sha1":"aae685f5f491bcbfcdac4f89fe135aef2832d3e5"},{"version":"3.0.0-alpha2","sha1":"7b6bdd9fcd21b0983d98c2ff18b79d51727c3e87"},{"version":"3.0.0-alpha3","sha1":"787e15be58536d222628060bfe4fb5a746fe1432"},{"version":"3.0.0-alpha4","sha1":"67d7c235d088233a5406859348f3886ac32d7c4e"},{"version":"3.0.0-alpha5","sha1":"944231ef35d4e92a26d0ae7bdf77aa995db7b357"},{"version":"3.0.0-alpha6","sha1":"1885f410011f09e67114f18dc5aeea2c2933bb6b"},{"version":"3.0.0-alpha7","sha1":"5eea5e8af4dfe3a81e91978d457be8079fb7968d"},{"version":"3.0.0-alpha8","sha1":"1acac02dd88d8f323535f9e33109ced5f5b8fa55"},{"version":"3.0.0-alpha9","sha1":"8332ccd8b657b6a157f8b0459bf0533efe0c9de6"},{"version":"3.0.0-beta1","sha1":"db7f5cca87f09eec7cd79a67cb54a9adc70bbbec"},{"version":"3.0.0-beta2","sha1":"cada9a710616545db860d344e168729571208686"},{"version":"3.0.0-beta3","sha1":"5c5b19083bbbe6085747272a49439b25c7501a78"},{"version":"3.0.0-beta4","sha1":"22ffc669c9f96155d68fc855aae261b457d6ad"},{"version":"3.0.0-beta5","sha1":"70d6e877f0818a915ede717f707c0b2d5ea04873"},{"version":"3.0.0-beta6","sha1":"ee9ff486dad21a5b76d0800b44e7a908e8475d17"},{"version":"3.0.0-beta7","sha1":"d76b082c26de245634ca0c5ff1866e6b091dccbb"},{"version":"3.0.0-rc1","sha1":"7bc07fc941f4cfb2ef1adb4087a241bc9e3b5423"},{"version":"3.0.0-rc2","sha1":"4f77e8864b6a85442e00a07e819eac97117c875"},{"version":"3.0.0","sha1":"b4b02fa623c5a618e68478d9a4a67e1e87c023c6"},{"version":"3.0.1","sha1":"c2945119335b491ca56e4042f7c8c55dccf5c9c2"},{"version":"3.1.0-alpha01","sha1":"29b41395de509252a58d99374a78f918be531db6"},{"version":"3.1.0-alpha02","sha1":"485fa4d3ac1858126f78ff3e6512e911d7460e6d"},{"version":"3.1.0-alpha03","sha1":"2bc4bb9908f9186f93f727e554c61cdcd03bee21"},{"version":"3.1.0-alpha04","sha1":"75bc47916b99517ae12d18ea6efb811f4d8144a5"},{"version":"3.1.0-alpha05","sha1":"7a1f0f56db0ecb2f3c53004cb169ff5b01757aac"},{"version":"3.1.0-alpha06","sha1":"acb22066ea961fc8f52655a4c0040edfe2ebd8b"},{"version":"3.1.0-alpha07","sha1":"87f0f67a87bdec2760da87984f129499e48aae7b"},{"version":"3.1.0-alpha08","sha1":"8009c4051b206ffd4ba7f272968a829a5d6bf3c2"},{"version":"3.1.0-alpha09","sha1":"ccb4814494d720f95fe7a20824d81f920e059ef3"},{"version":"3.1.0-beta1","sha1":"fda5738e927d783d01bcb3a30e82bbc5bbfaab29"},{"version":"3.1.0-beta2","sha1":"2cdd410f0c3d0775be4fdff11c42f39d50a74113"},{"version":"3.1.0-beta3","sha1":"f3b44ab507d71e1d880e5ca58c5e248b81f4b688"},{"version":"3.1.0-beta4","sha1":"13443212b69d887c8c50b6a4e198ed72e957b829"},{"version":"3.1.0-rc01","sha1":"e623929395c640b794553ca3111e95f1bf573e04"},{"version":"3.1.0-rc02","sha1":"fbc5c5f6a4df7602e60cf907a585289041e4b260"},{"version":"3.1.0-rc03","sha1":"bcdbbe6fccaf7e2a9e0ed2c2c5fa5be5a382ea97"},{"version":"3.1.0","sha1":"7845e5588a1da9dad9429f12e441385a35e0ca02"},{"version":"3.1.1","sha1":"250777904e400a9c3adbe08b9c287125e10cdeec"},{"version":"3.1.2","sha1":"ccab33656c1baa6514d88f4d9356db19d0e9823b"},{"version":"3.1.3","sha1":"29ebfa3775979d9cb51b4c428d741067040d2298"},{"version":"3.1.4","sha1":"4c846d065331c2a11ed605619613833a842a7b8d"},{"version":"3.2.0-alpha01","sha1":"7b9862cc7869f3273259aae668cace4a790c742a"},{"version":"3.2.0-alpha02","sha1":"6254eaa5af937da64148fdc22d4de02cf06d40af"},{"version":"3.2.0-alpha03","sha1":"5eff579d96c72df4f65ba7f2c5335bdd527f3847"},{"version":"3.2.0-alpha04","sha1":"2e8890e153fe53c4f67626013b17ddc67a4a9a9d"},{"version":"3.2.0-alpha05","sha1":"156e2daba4d0a6d939091ad74a0578663be3020d"},{"version":"3.2.0-alpha06","sha1":"f0df427b9f88c553d5d3bd80dd39fe5cbf1666e3"},{"version":"3.2.0-alpha07","sha1":"66edbd77bc407ce9c76d89ba65ff173eea2683ef"},{"version":"3.2.0-alpha08","sha1":"421f914c2d337f5bc70754a29ad497845dd8812e"},{"version":"3.2.0-alpha09","sha1":"c4b4648c6d8df07ec5c9ccedc95620fb7fbd5b7c"},{"version":"3.2.0-alpha10","sha1":"4c2a94af936e07f8b7d3bcd18f87e56c044be42b"}]},{"package":"aapt2-proto","versions":[{"version":"0.1.0","sha1":"d1eb93a21a8d3590c3bfac574a8b6dffb2dbd21c"},{"version":"0.2.0","sha1":"462d00a275701932a04c63ba652a1e9f69e2c029"},{"version":"0.3.0","sha1":"49fcfeb3bfe5cda96bb38a6638cdc1e0ba53e81b"},{"version":"0.3.1","sha1":"8311800abb3dcd84426a0de834a6b884a173d864"},{"version":"0.4.0","sha1":"d4db96aa0d2e161d08a038731e08c3ebf612cd12"},{"version":"3.6.0-alpha01-5435860","sha1":"ca4908f5a2d896c06433dc77cdd0fd84029e1ead"},{"version":"3.6.0-alpha02-5435860","sha1":"b8ec46c90645b4d57fd7f9ba931e29eaf49c6104"},{"version":"3.6.0-alpha03-5516695","sha1":"f0b6e918ec2d4bb536dec9e2fd30f3025bed89f8"},{"version":"3.6.0-alpha04-5638319","sha1":"f8ccbbba2f30acaddc79ed41395de61d87fc95bd"}]},{"package":"bundletool","versions":[{"version":"0.1.0-alpha01","sha1":"f7c303e37818223bd98566fcbea29aa0964c4d06"},{"version":"0.1.0-alpha02","sha1":"dc3b1ee454e9bc0c6b04fce143305573baa350f5"},{"version":"0.1.0-alpha03","sha1":"ee87bf0769a1c8e1e687c2c943f750fe45282b36"},{"version":"0.1.0-alpha04","sha1":"f089ca28affa96df2d8ec33a43f59e29d3f5377c"},{"version":"0.1.0-alpha05","sha1":"836fd61644f1bb5bf6a48259abb68d81c1045088"},{"version":"0.2.0-alpha01","sha1":"f818d4d224d0e8f9bd32a967d2677e514963f69b"},{"version":"0.2.0-alpha02","sha1":"4f589a09aedbf3488da7df9c274042674575839b"},{"version":"0.2.0-alpha03","sha1":"4a79ebbd9531263618d5a443e1fca2895c22f828"},{"version":"0.2.0","sha1":"a1afbc66241ed35ddbde6d45ecd7887d6558b90a"},{"version":"0.2.1","sha1":"c002214c49e89ca2884ca6bb9a7638dfdddf57a7"},{"version":"0.2.2","sha1":"4b4a83e09856ff67f29979fb64964e181196f923"},{"version":"0.2.3","sha1":"3271832aa3ea22930840356f631e4ee3eb00a43b"},{"version":"0.3.0","sha1":"e56a773a82a7681e06e88ff5b27859a7efd5b3a8"},{"version":"0.3.1","sha1":"542904fdf26e0d8a3e28166a1f7adcfd5f668c15"},{"version":"0.3.2","sha1":"9ac5172a38af4bb3c4e89eee9df8e382234a87b"},{"version":"0.3.3","sha1":"4155f9bcbf718adc5760060daf0170bfc68fec50"},{"version":"0.4.0","sha1":"1dcd903e74f3fe60619e3dfe116f81bbd6139350"},{"version":"0.4.1","sha1":"c7fe95bff1970044b6ba111551e95287f3bbfaad"},{"version":"0.4.2","sha1":"39951d6b51ba251052b75b740070089003a6d71b"},{"version":"0.5.0","sha1":"ffd6077d13937213b860c0022174ece60000eabc"},{"version":"0.5.1","sha1":"635368477286f31d557045e241ec59fce87041da"},{"version":"0.6.0","sha1":"fb944be26f692e6ac937924ad5be7faa4093dd80"},{"version":"0.6.1","sha1":"889c8495f7b0e18d7971350c2cbe6695d92b63d7"},{"version":"0.6.2","sha1":"ec0a0cdb87a4fcceae72379b00c5d75d9505bf6a"},{"version":"0.7.0","sha1":"41f2c764308a58067cc93b928287be91c7ba1f65"},{"version":"0.7.1","sha1":"e1623774e1434fa379a1ab340f0acabeaa939424"},{"version":"0.7.2","sha1":"31f71b66edcbe41de0268e14c961a7799a03d42a"},{"version":"0.9.0","sha1":"6c7492aedf12ff02ed7c634246026ac7f7372dd2"},{"version":"0.10.0","sha1":"992d3f30afcf1876616a1afd4a79a61038119225"},{"version":"0.10.1","sha1":"8042b37d8b18d66964df26deb62d5cf64b9f00fb"}]},{"package":"apkzlib","versions":[{"version":"3.2.0-alpha04","sha1":"125a3d5422540d537465ff45bfb9f9c7fbf83b2"},{"version":"3.2.0-alpha05","sha1":"b994db5ae5642867016d1860fda87105d4a77ad5"},{"version":"3.2.0-alpha06","sha1":"3297df414582fd8b281d71404e7e6faaaf8d986"},{"version":"3.2.0-alpha07","sha1":"fa66ffc0ff84d4217e660ec8fee3ebeca39b5a5d"},{"version":"3.2.0-alpha08","sha1":"51c8de3707febd9f0a167b71a6295c80c313d5ba"},{"version":"3.2.0-alpha09","sha1":"71a570d1a778385acf8e260f7db39f5a7be0ea7c"},{"version":"3.2.0-alpha10","sha1":"4dbd8daf0d66be8ab5e5cc60bdd2cd8dd9c5e7b9"},{"version":"3.2.0-alpha11","sha1":"2117db98e2d80e43e8d5c82ffc7c6cbb9453cdcd"},{"version":"3.2.0-alpha12","sha1":"99ce6cb99b3b9136c21ddf9c65cb0fd02683d00d"},{"version":"3.2.0-alpha13","sha1":"39b909b81e3d39e44ae94ce6eaa2c3cb661a7424"},{"version":"3.2.0-alpha14","sha1":"9612ddc77edd5ccee60edac12ab37edb3a4ea844"},{"version":"3.2.0-alpha15","sha1":"e5d0392f016cee7333c5d98e99d53a15fef2615f"},{"version":"3.2.0-alpha16","sha1":"2efc4ecbbdcada004073cd55d865c0b75a0d71a2"},{"version":"3.2.0-alpha17","sha1":"9a640c5c337953c95d687501f8c6d843a0a3540"},{"version":"3.2.0-alpha18","sha1":"5279ee4904b759225455901a63ff69acb150883"},{"version":"3.2.0-beta01","sha1":"c0e3e9e977e3a33d850c83ccd5440d5a84d2521c"},{"version":"3.2.0-beta02","sha1":"35bb407c6770c0471291e2bfece3fdf8ce952d27"},{"version":"3.2.0-beta03","sha1":"8b7e6e0a362a71b84aa4ab07d60b6eb1b1bc3dff"},{"version":"3.2.0-beta04","sha1":"ecc03856c0d7baa5e4eeb0cd7450320bb67083a0"},{"version":"3.2.0-beta05","sha1":"261feae9e54883fc4f86d6e3d2f1eb6a84688acc"},{"version":"3.2.0-rc01","sha1":"9fac6aedfab74e0834a1f11b050199ab7128c5e4"},{"version":"3.2.0-rc02","sha1":"73552df8b1b072da69d0c3467fd3e6f73c3643de"},{"version":"3.2.0-rc03","sha1":"2cd2a77c297626eb61eed1587ec7c81dd1776501"},{"version":"3.2.0","sha1":"7cca0e96fcc7020726dc91572955313b68f90709"},{"version":"3.2.1","sha1":"fd288c2a8a84c8acb4e43cbbd9dc34fd442b8bfb"},{"version":"3.3.0-alpha01","sha1":"1617d2ecfc048e34badfeb9f160616501b44fabf"},{"version":"3.3.0-alpha02","sha1":"7354f7216430ef933076759d5c44c030713e9639"},{"version":"3.3.0-alpha03","sha1":"feffa65a0e829c486e59675ee092351ac01bc7d0"},{"version":"3.3.0-alpha04","sha1":"93e7c9ccad579d463087db9488c3f9c79cee34c3"},{"version":"3.3.0-alpha05","sha1":"d2c80ad1d0cca08f50d129e4933071e16b37d11c"},{"version":"3.3.0-alpha06","sha1":"28a733365f7b991e9671c49a14c0b67e97ed7112"},{"version":"3.3.0-alpha07","sha1":"9e7aee57ee5770434ad745ad77ef697479af7ac9"},{"version":"3.3.0-alpha08","sha1":"c13cd8aa93359caa6bb35edb4307fb076ccf3897"},{"version":"3.3.0-alpha09","sha1":"a11539beca4eae62d3c58a48d0679c8437d6e9a3"},{"version":"3.3.0-alpha10","sha1":"2c0c36445607d6fa6bb4978272ed1c116467ee4f"},{"version":"3.3.0-alpha11","sha1":"7d8cf458e3beb54750f30e80b745f56d33d46c8a"},{"version":"3.3.0-alpha12","sha1":"b9d4b8319c490a3302da01127cb7d6bffec98be9"},{"version":"3.3.0-alpha13","sha1":"7b4785e4a5f38703a312d89fe9bf1f67c15b5202"},{"version":"3.3.0-beta01","sha1":"c2fa11ead2930759654a1735094fd431533b08c2"},{"version":"3.3.0-beta02","sha1":"a37daed118de706efe92eedb632cc12a49ce3a2c"},{"version":"3.3.0-beta03","sha1":"1e7bdb9ec522351be018850ee3732684f97e4fe3"},{"version":"3.3.0-beta04","sha1":"a52269bda6b0ab6a68f1df03c2221ce3fcacf2b7"},{"version":"3.3.0-rc01","sha1":"b98f5989a43abc7e459b628ee2c939b9bf885317"},{"version":"3.3.0-rc02","sha1":"7a65a50a512c46e81debdea6a9e0c90b60cddf94"},{"version":"3.3.0-rc03","sha1":"188b424522d2fe0a553c23ccda40f93c1582815e"},{"version":"3.3.0","sha1":"e43305e4121635b1b92a1fb84b0b7f98dcb50691"},{"version":"3.3.1","sha1":"563a5b71668b4fe7da03a0a3054c9fdd973d3cf"},{"version":"3.3.2","sha1":"50596c8bb32cdebfcd83569fdb02ca30d0d0b938"},{"version":"3.4.0-alpha01","sha1":"77fec1aa0671107535c9764b90c11b377c78a672"},{"version":"3.4.0-alpha02","sha1":"27b03de6d03568b8caa6ce3a62d12dfe58fcc0fc"},{"version":"3.4.0-alpha03","sha1":"a2b81192a4d2d1bb8fa2cc47a851e49f5773b951"},{"version":"3.4.0-alpha04","sha1":"aa357764ca527a6cdfcba97ab078e86cfe8aa198"},{"version":"3.4.0-alpha05","sha1":"52b98e1c17bc0557623d77104a697cac1c634370"},{"version":"3.4.0-alpha06","sha1":"121b8324c310685552c4294aa8dd0136cb03fc00"},{"version":"3.4.0-alpha07","sha1":"c62098bd0808c652cfabc51053ec66a93bb371dd"},{"version":"3.4.0-alpha08","sha1":"e7bdd5daf4ca764ede527906526333569591063a"},{"version":"3.4.0-alpha09","sha1":"aca4aed2cbd59efe9ddca3cf4cbad2324b2888d4"},{"version":"3.4.0-alpha10","sha1":"bba2bf0e95c7307213a6721f1ec8700bcfcdfd43"},{"version":"3.4.0-beta01","sha1":"f307706c704675d503667a787c399f509f9319f0"},{"version":"3.4.0-beta02","sha1":"f6bbee25563d1759985d7287c03d2ccfe72bb3a7"},{"version":"3.4.0-beta03","sha1":"5bf5231cc0099f2ac9bb953abc4aba81df8c7207"},{"version":"3.4.0-beta04","sha1":"cab7dbf6277e332e68be630bdcaddd69aadda566"},{"version":"3.4.0-beta05","sha1":"d9401292e21a044e7cbf13785ce124c5e5eed96b"},{"version":"3.4.0-rc01","sha1":"dec9617a2c3df3e63e868618f298e3d0589fb763"},{"version":"3.4.0-rc02","sha1":"3cc76ac29e0a7e809e2ebf94b6968dd48f226638"},{"version":"3.4.0-rc03","sha1":"efb73542bd7b8a1f46e19bfa8819f9319f2dd524"},{"version":"3.4.0","sha1":"3cd3ade50ba5b812f77e24cb24e5fa9c567cf156"},{"version":"3.4.1","sha1":"9b754a18adcd2bec05ef9d88c776c16c292440"},{"version":"3.4.2","sha1":"71d09c181284340f2c0f9f2fa9e0bf71a239bd3e"},{"version":"3.5.0-alpha01","sha1":"c8339db9ccf61be2c531ebe548016025b0607732"},{"version":"3.5.0-alpha02","sha1":"8005dec43c51028ed2899cba5be7b9dd27d1f332"},{"version":"3.5.0-alpha03","sha1":"3ea5dd70bf677931c06ecc675607ee73d6204ae7"},{"version":"3.5.0-alpha04","sha1":"878966a5646e740f0e22683b49783c0d1985f7c7"},{"version":"3.5.0-alpha05","sha1":"272e7fbd9849d1bdf1a1b8b0702edcf0144c0916"},{"version":"3.5.0-alpha06","sha1":"682febc9dfd53098f27437ab3f9daadc39bbc03e"},{"version":"3.5.0-alpha07","sha1":"bb16bc03aeaf1d830361636607605b95c9071bed"},{"version":"3.5.0-alpha08","sha1":"332fb43d8b9384e3e852ee4235aab70d6e171edc"},{"version":"3.5.0-alpha09","sha1":"4f99ea16448b4aecde602c261bb329b13da49669"},{"version":"3.5.0-alpha10","sha1":"fea6fec782804ee9c2a4b10ef98e755964527645"},{"version":"3.5.0-alpha11","sha1":"ca3edcdd535f6a7f3a2db81538a3b0edc7deb990"},{"version":"3.5.0-alpha12","sha1":"162b34949570d0b1dcda720475545dd7d545b3cf"},{"version":"3.5.0-alpha13","sha1":"bd309ae8b4a9bc75d33e0e80bd9703b69425e1d1"},{"version":"3.5.0-beta01","sha1":"2d70480512adabda818ee154f695e1b2da60c6f8"},{"version":"3.5.0-beta02","sha1":"bc13148c9ef384b9bf919f7dec0a371a705a364b"},{"version":"3.5.0-beta03","sha1":"9658ece8c7d7ac4f5f14aac75cd9677cec5cb9e"},{"version":"3.5.0-beta04","sha1":"2d180545d75a443553f6858841f23435c0f0c189"},{"version":"3.5.0-beta05","sha1":"88334ec1a2648b4c0cfe6087bc25f4f8eb2f9ad9"},{"version":"3.6.0-alpha01","sha1":"33be4baad7352f4ff3d84fe7a6673367e0598a"},{"version":"3.6.0-alpha02","sha1":"1c6a6adc64f0b98cc2f0cb324aa3fad155dbb808"},{"version":"3.6.0-alpha03","sha1":"70f80ca56cdffb8a4c4d4574c95c73d9c8592d09"},{"version":"3.6.0-alpha04","sha1":"c64e78286f926522459da3326017c231ebe6e5fc"}]},{"package":"aapt2","versions":[{"version":"3.2.0-alpha08-4635615","sha1":"missingresource"},{"version":"3.2.0-alpha09-4662957","sha1":"missingresource"},{"version":"3.2.0-alpha10-4662957","sha1":"missingresource"},{"version":"3.2.0-alpha11-4662957","sha1":"missingresource"},{"version":"3.2.0-alpha12-4662957","sha1":"missingresource"},{"version":"3.2.0-alpha13-4662957","sha1":"missingresource"},{"version":"3.2.0-alpha14-4748712","sha1":"missingresource"},{"version":"3.2.0-alpha15-4748712","sha1":"missingresource"},{"version":"3.2.0-alpha16-4748712","sha1":"missingresource"},{"version":"3.2.0-alpha17-4804415","sha1":"missingresource"},{"version":"3.2.0-alpha18-4804415","sha1":"missingresource"},{"version":"3.2.0-beta01-4818971","sha1":"missingresource"},{"version":"3.2.0-beta02-4818971","sha1":"missingresource"},{"version":"3.2.0-beta03-4818971","sha1":"missingresource"},{"version":"3.2.0-beta04-4818971","sha1":"missingresource"},{"version":"3.2.0-beta05-4818971","sha1":"missingresource"},{"version":"3.2.0-rc01-4818971","sha1":"missingresource"},{"version":"3.2.0-rc02-4818971","sha1":"missingresource"},{"version":"3.2.0-rc03-4818971","sha1":"missingresource"},{"version":"3.2.0-4818971","sha1":"missingresource"},{"version":"3.2.1-4818971","sha1":"missingresource"},{"version":"3.3.0-alpha01-4818971","sha1":"missingresource"},{"version":"3.3.0-alpha02-4818971","sha1":"missingresource"},{"version":"3.3.0-alpha03-4818971","sha1":"missingresource"},{"version":"3.3.0-alpha04-4818971","sha1":"missingresource"},{"version":"3.3.0-alpha05-4916870","sha1":"missingresource"},{"version":"3.3.0-alpha06-4916870","sha1":"missingresource"},{"version":"3.3.0-alpha07-4916870","sha1":"missingresource"},{"version":"3.3.0-alpha08-4963834","sha1":"missingresource"},{"version":"3.3.0-alpha09-4963834","sha1":"missingresource"},{"version":"3.3.0-alpha10-4982317","sha1":"missingresource"},{"version":"3.3.0-alpha11-4982317","sha1":"missingresource"},{"version":"3.3.0-alpha12-5013011","sha1":"missingresource"},{"version":"3.3.0-alpha13-5013011","sha1":"missingresource"},{"version":"3.3.0-beta01-5013011","sha1":"missingresource"},{"version":"3.3.0-beta02-5013011","sha1":"missingresource"},{"version":"3.3.0-beta03-5013011","sha1":"missingresource"},{"version":"3.3.0-beta04-5013011","sha1":"missingresource"},{"version":"3.3.0-rc01-5013011","sha1":"missingresource"},{"version":"3.3.0-rc02-5013011","sha1":"missingresource"},{"version":"3.3.0-rc03-5013011","sha1":"missingresource"},{"version":"3.3.0-5013011","sha1":"missingresource"},{"version":"3.3.1-5013011","sha1":"missingresource"},{"version":"3.3.2-5309881","sha1":"missingresource"},{"version":"3.4.0-alpha01-5013011","sha1":"missingresource"},{"version":"3.4.0-alpha02-5013011","sha1":"missingresource"},{"version":"3.4.0-alpha03-5013011","sha1":"missingresource"},{"version":"3.4.0-alpha04-5013011","sha1":"missingresource"},{"version":"3.4.0-alpha05-5013011","sha1":"missingresource"},{"version":"3.4.0-alpha06-5013011","sha1":"missingresource"},{"version":"3.4.0-alpha07-5013011","sha1":"missingresource"},{"version":"3.4.0-alpha08-5013011","sha1":"missingresource"},{"version":"3.4.0-alpha09-5013011","sha1":"missingresource"},{"version":"3.4.0-alpha10-5013011","sha1":"missingresource"},{"version":"3.4.0-beta01-5013011","sha1":"missingresource"},{"version":"3.4.0-beta02-5209885","sha1":"missingresource"},{"version":"3.4.0-beta03-5209885","sha1":"missingresource"},{"version":"3.4.0-beta04-5252756","sha1":"missingresource"},{"version":"3.4.0-beta05-5303311","sha1":"missingresource"},{"version":"3.4.0-rc01-5326820","sha1":"missingresource"},{"version":"3.4.0-rc02-5326820","sha1":"missingresource"},{"version":"3.4.0-rc03-5326820","sha1":"missingresource"},{"version":"3.4.0-5326820","sha1":"missingresource"},{"version":"3.4.1-5326820","sha1":"missingresource"},{"version":"3.4.2-5326820","sha1":"missingresource"},{"version":"3.5.0-alpha01-5013011","sha1":"missingresource"},{"version":"3.5.0-alpha02-5228738","sha1":"b5f90e0b9f830622fff24eb72a42168a1795da00"},{"version":"3.5.0-alpha03-5252756","sha1":"a6a1d623986df46494bc0c4ba7ab2f0facc5081b"},{"version":"3.5.0-alpha04-5252756","sha1":"d5d48c60aa5e6b5bba9a773effe3f087368719fb"},{"version":"3.5.0-alpha05-5303311","sha1":"86f0402c08876309f94c1a25ef87dd6fb435d2b8"},{"version":"3.5.0-alpha06-5320053","sha1":"2d4008287dc04a20f849d542e85c8227bbb2a87f"},{"version":"3.5.0-alpha07-5336736","sha1":"409a9a34245de4acb83b4791216204a08a3275a6"},{"version":"3.5.0-alpha08-5342889","sha1":"bd1d227d1f8d8372c1bdc4d245625288e6cf5aeb"},{"version":"3.5.0-alpha09-5342889","sha1":"b6669e39e6f293255274b79107eb191dceaff4df"},{"version":"3.5.0-alpha10-5342889","sha1":"452dd14c9ab5c8ac7e98c386213a4323d31c29f8"},{"version":"3.5.0-alpha11-5342889","sha1":"986da70312bb2502c9c5fc7a5c2877a96019b19d"},{"version":"3.5.0-alpha12-5342889","sha1":"2875d51dd6861f3095a566df7acfef725ed56029"},{"version":"3.5.0-alpha13-5435860","sha1":"e459d9c480ac6a051f32a00c327da1b781df526c"},{"version":"3.5.0-beta01-5435860","sha1":"cf87fad244043872ac36e53797aa7f6f84164d6"},{"version":"3.5.0-beta02-5435860","sha1":"a5186821719c6b4d814e360f195eeb8b2724f402"},{"version":"3.5.0-beta03-5435860","sha1":"43b1fb139e9dbf2b91a37f990e600f9ec05c915f"},{"version":"3.5.0-beta04-5435860","sha1":"4a15066e6400f0f073957a1ab1549062669fbc05"},{"version":"3.5.0-beta05-5435860","sha1":"560bb380ea447d640b6641747e96edab3c5f9990"},{"version":"3.6.0-alpha01-5435860","sha1":"6731cfb04fcb511b02814c6210716b27a9b6921e"},{"version":"3.6.0-alpha02-5435860","sha1":"7f2d1c6717d9e34e53e96339a68e6b18ae72a19d"},{"version":"3.6.0-alpha03-5516695","sha1":"1d360505daa842ef632ff8eb27ed102cba74e6a7"},{"version":"3.6.0-alpha04-5638319","sha1":"adf1f0784f6eeec18204c65858367aa6ba7e6f15"}]},{"package":"aaptcompiler","versions":[{"version":"3.6.0-alpha04","sha1":"dfff8fb270cbdc941ad9c70021a6b312f29374bd"}]}]},{"group":"com.android.tools.analytics-library","update_time":-1,"packages":[{"package":"tracker","versions":[{"version":"26.0.0-alpha1","sha1":"cd3f7b8d341aa108b24d86928969f621538f08c4"},{"version":"26.0.0-alpha2","sha1":"c31e96aff126bc597e1ef5b425fcaff7705740b1"},{"version":"26.0.0-alpha3","sha1":"c6499730fa0c9020f83ed6f08d2aa86284fadbe7"},{"version":"26.0.0-alpha4","sha1":"2b519a03b2719025e07307cac6c5afa4c302bbce"},{"version":"26.0.0-alpha5","sha1":"7f6516ab34797ab1fb67188923594bc9006114ad"},{"version":"26.0.0-alpha6","sha1":"486dd5689787e4f8ec9cf59bb81675e60a17c905"},{"version":"26.0.0-alpha7","sha1":"bd99d1cf87b95e449946133769d98d0110573030"},{"version":"26.0.0-alpha8","sha1":"9a3df1fa28fa5667fff8160c6ef31329fd891de3"},{"version":"26.0.0-alpha9","sha1":"f94f79bb5f35a5a650accacb7e636595ce10f421"},{"version":"26.0.0-beta1","sha1":"f84d94b1f11ee3c9e6cb64281119679ee08ab82e"},{"version":"26.0.0-beta2","sha1":"3703efdec4ec0d56ed633a7602d3528351263eb4"},{"version":"26.0.0-beta3","sha1":"bb484f8e4c68aacf909a23f0ff528e5987990901"},{"version":"26.0.0-beta4","sha1":"99331b89316416cf1e8fd3b8bafa5d6d5b705d2e"},{"version":"26.0.0-beta5","sha1":"518bb0c3182da769b07d1451db2ea8a9ac4061cb"},{"version":"26.0.0-beta6","sha1":"41456f9e55e83efdc0abf11b2b2b608d83fc0159"},{"version":"26.0.0-beta7","sha1":"576df5cbbea2fc921b16c13442e11a2ab7d6e22f"},{"version":"26.0.0-rc1","sha1":"a56b2f36cf40a434a9e36f65e6735b3b62ebdc49"},{"version":"26.0.0-rc2","sha1":"ab24d939ea294d0bf7a4f89372cf74a4a0d07982"},{"version":"26.0.0","sha1":"f75df0e15d6db4ed1ba42e9d22bde29868db3f0f"},{"version":"26.0.1","sha1":"15f0c0560c8a97700651d2dd5219937baa80c166"},{"version":"26.1.0-alpha01","sha1":"f64d3b9ffb0cf47f0458f042043f5cb3bc0da023"},{"version":"26.1.0-alpha02","sha1":"a933ac011db3aa7c17b6ee7d36e8fb33325797ce"},{"version":"26.1.0-alpha03","sha1":"2b82be781a57e4f80aec0244180924cf30c6ae71"},{"version":"26.1.0-alpha04","sha1":"d3356ee424c45ecee0fdd084faa496a57ac84c68"},{"version":"26.1.0-alpha05","sha1":"4caa7f9cf8fe805e1eb08f8435221c103d10c962"},{"version":"26.1.0-alpha06","sha1":"c894faba6e9ddf991fb296af670c3993c6332169"},{"version":"26.1.0-alpha07","sha1":"badaaf5bd0eaf5192ddc937532de2f7c69122200"},{"version":"26.1.0-alpha08","sha1":"8ba603718b9190814ff1521bd85178e5651314c5"},{"version":"26.1.0-alpha09","sha1":"1034a838b337820060c4cf96a88029fa5703aa11"},{"version":"26.1.0-beta1","sha1":"37ed7e5b1d130494e410c47ed2e8f867c12c50b"},{"version":"26.1.0-beta2","sha1":"f03a1dd0b45c72722ce37ac1d96abf390ad29e53"},{"version":"26.1.0-beta3","sha1":"727f5fe7b5f4d0f9e8bc94edf42e99fba1d62bc8"},{"version":"26.1.0-beta4","sha1":"e2ed3506002639364547d77bce6b73f08dcd9192"},{"version":"26.1.0-rc01","sha1":"76e040e920f93ea709ccf981180681d5b474c0a3"},{"version":"26.1.0-rc02","sha1":"d3cb3fc85f4d1831e60253c520050a314d8ed5de"},{"version":"26.1.0-rc03","sha1":"f7a0b7c6364f0fe215b863aa1bea247a2e14bcf8"},{"version":"26.1.0","sha1":"583f8f9c850878264ef658c848f616da86c6a47e"},{"version":"26.1.1","sha1":"d209e62c9b06c1580443a275171f6d00089eef6d"},{"version":"26.1.2","sha1":"2d2260da92e50ac072f89d60a596d03aab3a8757"},{"version":"26.1.3","sha1":"485629aec0a5ee29722b798a62d7c9eeca9ce91"},{"version":"26.1.4","sha1":"fd48066d6c78b78abf5baa335dc2124060e8371c"},{"version":"26.2.0-alpha01","sha1":"97b06f662fdfcea49825798cc0826afaba8d9d66"},{"version":"26.2.0-alpha02","sha1":"467c6a9faa82e1ab8fa264ddc06667f5e1cfca9"},{"version":"26.2.0-alpha03","sha1":"6cd662ee885b18fd6cc71e89d0d720c6bd382029"},{"version":"26.2.0-alpha04","sha1":"9a3943ddee1fa1c3512bf864fd67d40eb2057fa8"},{"version":"26.2.0-alpha05","sha1":"b0e9916f8908e65d4a60d4558ad7e4049bc97b65"},{"version":"26.2.0-alpha06","sha1":"a3269e3669112eb8e63c5cb5046f09c892b78e41"},{"version":"26.2.0-alpha07","sha1":"136dbf06d2e2e5ec70a13e358a81ec5c67bbfabf"},{"version":"26.2.0-alpha08","sha1":"d66cfb16c51d31642076827a9ca4d9255de0ca6"},{"version":"26.2.0-alpha09","sha1":"ffe72a44aafaa128672a4c7bce8a126b71bf345a"},{"version":"26.2.0-alpha10","sha1":"136722d386cac077afa267e2abc06cff9d5bee92"},{"version":"26.2.0-alpha11","sha1":"d7b53b3dd8d27e41851a71dbe54ccbb926bd04f9"},{"version":"26.2.0-alpha12","sha1":"3f40de13c577ebddb8fe72da21d72fe44f400843"},{"version":"26.2.0-alpha13","sha1":"4ab11eb65b3c7d9f76cf68dcf7b52ffb09817b38"},{"version":"26.2.0-alpha14","sha1":"f90be09cb50d000a539064a45acd21c74f0ef156"},{"version":"26.2.0-alpha15","sha1":"ba8826f249c9f775424737f71f9bc3f9257c78ee"},{"version":"26.2.0-alpha16","sha1":"e282cf15fdb954e08b6fe6c7eb72fde08813a25"},{"version":"26.2.0-alpha17","sha1":"b3b34533d06eccb5d865ae92134204b8da185b24"},{"version":"26.2.0-alpha18","sha1":"17554d1eb57e79b878c5005454b56b3b68823fcf"},{"version":"26.2.0-beta01","sha1":"f4953d642e0dab38fcd5d34d4ad880dd9d69fecd"},{"version":"26.2.0-beta02","sha1":"ec3a5b01229ad60623bb895238700ba1dc180890"},{"version":"26.2.0-beta03","sha1":"d0cc772ab5691827b62d9aba3e42714dbea7efcb"},{"version":"26.2.0-beta04","sha1":"d3f37663cb91717dbd1c94f867874d4ebbd5162"},{"version":"26.2.0-beta05","sha1":"963aa478b761c4e12c8278be33b45d22494aa49f"},{"version":"26.2.0-rc01","sha1":"a68c5ad375a950146ec013987f8ca2cc9eeadb32"},{"version":"26.2.0-rc02","sha1":"c563ae89c88261fa82292c5dee043322829ebab"},{"version":"26.2.0-rc03","sha1":"61afcdfb38e2b5f4c9811d4b1d78bd2cecd15872"},{"version":"26.2.0","sha1":"f7f19b1bf77f05efa0e63eb6f7a047753b25c1fb"},{"version":"26.2.1","sha1":"6b9eca42be508a8d87c287ad280a27f802f0e40a"},{"version":"26.3.0-alpha01","sha1":"a85bd8dc7a9ce21e05bc7358b23c6f87d849be67"},{"version":"26.3.0-alpha02","sha1":"7d2b650b09c9a7e0406e4a0141811de6c658ffd4"},{"version":"26.3.0-alpha03","sha1":"a8d496f9fb58010ce415b93eac74c67e903c5edd"},{"version":"26.3.0-alpha04","sha1":"ff01a3f4ce84b0bd155ce64e855283d06f77175a"},{"version":"26.3.0-alpha05","sha1":"ead4a79257425ad3df35bff6b0c94dc09c0134d3"},{"version":"26.3.0-alpha06","sha1":"86b47c01e66b93b25c9ae1721b653a2a0ed9c3c3"},{"version":"26.3.0-alpha07","sha1":"cad82c2c4d7da7ac94196fa7741e24732c08700e"},{"version":"26.3.0-alpha08","sha1":"cba0d45c699fb34d333f3f448d4f184203aeeee1"},{"version":"26.3.0-alpha09","sha1":"faaf9c5142cd327551f540fbbebff339a5a5d507"},{"version":"26.3.0-alpha10","sha1":"1991afd33984aa562f1284ca702af245c78c1a22"},{"version":"26.3.0-alpha11","sha1":"aa05c332d6bda5b91bf44118decb6f7492deac18"},{"version":"26.3.0-alpha12","sha1":"6595c75320e727830c07d4cf0d24a61b75305ad6"},{"version":"26.3.0-alpha13","sha1":"95d1c62e41f5ea65db471ec21d91330b9cadd18d"},{"version":"26.3.0-beta01","sha1":"72a3f6ddfd40d11685a9b2966286c32f70306ed5"},{"version":"26.3.0-beta02","sha1":"a64c6762c178a6c2fa67492badb86fde60f77350"},{"version":"26.3.0-beta03","sha1":"78e7a8f8b58a43ba4de50aa7dc7e5ea6cba849d"},{"version":"26.3.0-beta04","sha1":"c7f29ce700eb669651c99adad45c0f82289c04fd"},{"version":"26.3.0-rc01","sha1":"51cbe511e3d880fb3003d532f8353b5d77b1c013"},{"version":"26.3.0-rc02","sha1":"b56901de211c660185f728509e9bea210e16af51"},{"version":"26.3.0-rc03","sha1":"ae97e996b7fe83dd36d912235d1452ee6a12a678"},{"version":"26.3.0","sha1":"5c25a0bb3f6606bb5a31f93629b7eee84be59c08"},{"version":"26.3.1","sha1":"26248cad2525e003752322cfc6bd86182df7daac"},{"version":"26.3.2","sha1":"90b31d61ab6774e6b0dbf54e9561a2e51b7c8a45"},{"version":"26.4.0-alpha01","sha1":"c2de08cdd6e7ec126910c6e81d88cb6270860d05"},{"version":"26.4.0-alpha02","sha1":"6f6a9f6e7357c78c0be1571d59a0db5c0651325c"},{"version":"26.4.0-alpha03","sha1":"4a794bcfe8cb8dadc76a61d5cfa94084122543cf"},{"version":"26.4.0-alpha04","sha1":"e3bb355ceb74bd6d7e2085f571af327b773ad825"},{"version":"26.4.0-alpha05","sha1":"b0428d802d214ee8a0efee0969bb761fdc2f354c"},{"version":"26.4.0-alpha06","sha1":"a6f9870bf7661a7c7ddbd7950d78a6355a63c130"},{"version":"26.4.0-alpha07","sha1":"82348670af0c4773169ffa594f03d6ad052e1c9f"},{"version":"26.4.0-alpha08","sha1":"44055f32355af0569e46146763c7602ef0d39ec8"},{"version":"26.4.0-alpha09","sha1":"fd50b23f2c9053ac55434199b3c253fbb1fc2250"},{"version":"26.4.0-alpha10","sha1":"a5cee213b42625b2d330b8270f1a62d41211eb93"},{"version":"26.4.0-beta01","sha1":"88d654203e0957ee3b306c81f580952e1b6c4dd2"},{"version":"26.4.0-beta02","sha1":"cd51ed3e7232c89f1fdd626bfcf81bb78479b15d"},{"version":"26.4.0-beta03","sha1":"922e000a729d7fcec2cefec86499f9b974b0ccc5"},{"version":"26.4.0-beta04","sha1":"6654f1dd505fe017441729d9db6bc0b60f2b75e9"},{"version":"26.4.0-beta05","sha1":"352d670c8a5bf9dfadc404711573b0f6c9a5c147"},{"version":"26.4.0-rc01","sha1":"9a75446e3de454ccc8fe2c843e898d727a939b10"},{"version":"26.4.0-rc02","sha1":"d39f27d1ba98cb1976422ed364eb318cd80cecd2"},{"version":"26.4.0-rc03","sha1":"78cd446960e5ef1ca6c5275322bf49e2fa8cecc6"},{"version":"26.4.0","sha1":"b3f28a0cfe0b5424e280f97123e4f7d4b7aa2ce9"},{"version":"26.4.1","sha1":"2574526ca59f2ddf4bd0ed6c3f77ecd89b51512d"},{"version":"26.4.2","sha1":"70889c931e5ac4e028284438fc691847d95e97d8"},{"version":"26.5.0-alpha01","sha1":"3ecbbb6deaee80fece4dc50d10ee9c4e6f6919fe"},{"version":"26.5.0-alpha02","sha1":"4d317d238037b4fe252567ca86af057c0daeac11"},{"version":"26.5.0-alpha03","sha1":"5666c335099c8458448bf1f1e5b6eb51afed0058"},{"version":"26.5.0-alpha04","sha1":"ba5619d4c2d3716dad5579919e57c6fb9e69159e"},{"version":"26.5.0-alpha05","sha1":"fca957172b6db8119ce1a65da262ef0d5428c0e0"},{"version":"26.5.0-alpha06","sha1":"6d60769ea2b9c9f5517106c4e23bde28f0360522"},{"version":"26.5.0-alpha07","sha1":"13da9837965afe14c836c294bd41cf5bcd7d0d51"},{"version":"26.5.0-alpha08","sha1":"208978a2da9b490f58cc9b52b311ef43a25dfb3e"},{"version":"26.5.0-alpha09","sha1":"c7ba4535fb48e85a64512910f87f198bee3869ba"},{"version":"26.5.0-alpha10","sha1":"3ea9c9b97674f5354f1df1379804abfb618df255"},{"version":"26.5.0-alpha11","sha1":"ece5ae1f95877e0864c7f688a86959253e283f06"},{"version":"26.5.0-alpha12","sha1":"791f5410ea383c7cb903de058554e96ec46e9427"},{"version":"26.5.0-alpha13","sha1":"d0c32dc7b798d49101f8b6ccbf1768450b3fbbc2"},{"version":"26.5.0-beta01","sha1":"f2aadf4a7a313aede2d06db70e35f89ba1122f8a"},{"version":"26.5.0-beta02","sha1":"86a0258fd2f58e206191bf382d5d4d426944ba45"},{"version":"26.5.0-beta03","sha1":"e9fa0d31aead59df0c32826b5779c3f194eaa86f"},{"version":"26.5.0-beta04","sha1":"ada3f8ebce7045701afaaf75bedffebf705ed0c3"},{"version":"26.5.0-beta05","sha1":"4617f5b24fca109c5a1167a27d84afdd11bfcd6"},{"version":"26.6.0-alpha01","sha1":"4ee730a44d305c6bcff5e19b4a493e063e67463c"},{"version":"26.6.0-alpha02","sha1":"c5132e8040faae9f7f5e0d264ad053d1ae29c773"},{"version":"26.6.0-alpha03","sha1":"92bc694c388d0d5fdc017598d3915281d6ba253a"},{"version":"26.6.0-alpha04","sha1":"4fd622bf58b7298606d8f6b66acb52948d6425cd"}]},{"package":"protos","versions":[{"version":"26.0.0-alpha1","sha1":"4e08f74445b95c4e0b06b70f92908d704317ca81"},{"version":"26.0.0-alpha2","sha1":"dad096833c184d72e2cbbffd260f554fc2057a39"},{"version":"26.0.0-alpha3","sha1":"966e8c13067bef23d1c15e082a70917afca55c2b"},{"version":"26.0.0-alpha4","sha1":"d5631ee65db408578b77f10aa53e0b6a4acccf6a"},{"version":"26.0.0-alpha5","sha1":"55641712cb228901d670dc71d672712c78ebd4e2"},{"version":"26.0.0-alpha6","sha1":"804518aa159615219589796cea268acd3fda2767"},{"version":"26.0.0-alpha7","sha1":"a6967ab73c6081e608ed3495581e6046e72150ec"},{"version":"26.0.0-alpha8","sha1":"6a0a9c5168d6b08c88891dbda42284e14c3fb73f"},{"version":"26.0.0-alpha9","sha1":"e12905474a60f3d2e02c3c1beca691de66f0b311"},{"version":"26.0.0-beta1","sha1":"f8e9b825d38b22b891f44b0980b4ad369e121c8d"},{"version":"26.0.0-beta2","sha1":"cebce6137e5b8d8fb4d218012e2dc4ed299be12e"},{"version":"26.0.0-beta3","sha1":"ca7bc5f0bdf51cabdd3b6b5825a0645d5f3703b2"},{"version":"26.0.0-beta4","sha1":"55c69602771b8497e3526c1113b1572420fa7f8f"},{"version":"26.0.0-beta5","sha1":"85dfb40a47bb8833fa90904a6330bb9a54b6eab5"},{"version":"26.0.0-beta6","sha1":"d66c47515e1f00f24ee93a7a7ec20725776a028d"},{"version":"26.0.0-beta7","sha1":"84942eb64bf5173cdef14b6732a1eb5af9c0f798"},{"version":"26.0.0-rc1","sha1":"23cb0f4404516f60127a1b13a845d7ee547a17f0"},{"version":"26.0.0-rc2","sha1":"cd053baf6c7a3ad93f1571d5d7927f6cdcef4e7a"},{"version":"26.0.0","sha1":"9700759196d267fc7cf3c85806c157050ea37dc9"},{"version":"26.0.1","sha1":"7719e7ed7498e25ec555bdfcb7a4de0e88a47ea"},{"version":"26.1.0-alpha01","sha1":"3d76f19664b9825e924ce562ed135605ce4102dc"},{"version":"26.1.0-alpha02","sha1":"bee91c544bd2d6e64b26b1349a934c25795da707"},{"version":"26.1.0-alpha03","sha1":"7a1498b91d3d02fd11edebf3b5ca81b8c11ea7f5"},{"version":"26.1.0-alpha04","sha1":"76ef15531f657d2b122b4023c012b4ba22907c27"},{"version":"26.1.0-alpha05","sha1":"ce13e78cd5920b313d9c1c5c021d7fd179d91a62"},{"version":"26.1.0-alpha06","sha1":"d9d6dc0a791b2203857e198430885886a2bf2866"},{"version":"26.1.0-alpha07","sha1":"b91a0b0bca35802b39c92170fa2fe3ed85b177ff"},{"version":"26.1.0-alpha08","sha1":"75c09885a3f7af63bbcdd7668c0438bc15bd95c6"},{"version":"26.1.0-alpha09","sha1":"899b9128a0b8f5a5f6cbd5df918cdf65c7d37b92"},{"version":"26.1.0-beta1","sha1":"c3df20ba24c58a25bb34aaee8cf538db4401e33e"},{"version":"26.1.0-beta2","sha1":"14271baae6cb7de00b8f9e9b9adacb0bf4d1d08"},{"version":"26.1.0-beta3","sha1":"dd29208f1afe8945f683ce555397382a06610a24"},{"version":"26.1.0-beta4","sha1":"fe8d9051b233fe8a0311b7d480434019c1b1706a"},{"version":"26.1.0-rc01","sha1":"6a0162c87bab2b8a635d218034516eca23027f34"},{"version":"26.1.0-rc02","sha1":"c22fe9db6b2ef2da0f3b9d45d8e6e43c59bd5713"},{"version":"26.1.0-rc03","sha1":"53a44d75e628e121172000d6b530643966aaea98"},{"version":"26.1.0","sha1":"1e2d35fcc1668730a6c6be29e61fbd059278f243"},{"version":"26.1.1","sha1":"d58f5724467d21a4c9dd0faf235d038dc1002433"},{"version":"26.1.2","sha1":"ba53bcde9703b2bf9871128952dce844c5d743fa"},{"version":"26.1.3","sha1":"6940093b88cc71f2c0bf1525cc407be899d35b6f"},{"version":"26.1.4","sha1":"dc5876bb894d87d2088b0a4447b13fdc13ad82fc"},{"version":"26.2.0-alpha01","sha1":"9768d534c6c1449d47972ca6c6220e59e6b1f539"},{"version":"26.2.0-alpha02","sha1":"d9540a533305ff2e16bc856dc4ec6b09b76dbcbe"},{"version":"26.2.0-alpha03","sha1":"985939a96dbec481c253a70e5d36b658022ab0b4"},{"version":"26.2.0-alpha04","sha1":"f5d567f8e49d25213eb648fc01629e6ab78f9852"},{"version":"26.2.0-alpha05","sha1":"50e0680755bcd5b3cdf694ab603d21e6a2de88c7"},{"version":"26.2.0-alpha06","sha1":"38d49cc92ecad86391ec7bf6e6f4161e36fe0e99"},{"version":"26.2.0-alpha07","sha1":"572bb4941d7f6e59da2031b1f9cbcd2ba6b831be"},{"version":"26.2.0-alpha08","sha1":"875ce493ac0463ee5b0efa132c54e6dbb651ece"},{"version":"26.2.0-alpha09","sha1":"86226ed875d2aa2c000ddadab18ed4a2007378e5"},{"version":"26.2.0-alpha10","sha1":"750dbe1b1bb9d503cde1252a05cad42a09b84dd4"},{"version":"26.2.0-alpha11","sha1":"ed5f7bd04a96e0f2e22fca3b38f3f60d58f2e888"},{"version":"26.2.0-alpha12","sha1":"3835ce6013fdb177b1536384b5c5ff8df84cd298"},{"version":"26.2.0-alpha13","sha1":"c20eabaa0c69f566631d19e45bcb62695025dd8b"},{"version":"26.2.0-alpha14","sha1":"e28cd0ee304301b9305ab184a6b8658aac9e220a"},{"version":"26.2.0-alpha15","sha1":"2a6ec1864ab9eb4c6ed40c2d2b9de40eb0ded380"},{"version":"26.2.0-alpha16","sha1":"3bded0dc689cf5b9e2e31514e7a4ac106401ca21"},{"version":"26.2.0-alpha17","sha1":"8eba5ef8087278455381a664795f252008cd9529"},{"version":"26.2.0-alpha18","sha1":"139a3b128787d513a33ff07a2b7116c7722f23ce"},{"version":"26.2.0-beta01","sha1":"27d5d15f6c6cc186c31d15b5032e1d321d87a32"},{"version":"26.2.0-beta02","sha1":"acad5a2788752aea5db629814d75f4457a12d221"},{"version":"26.2.0-beta03","sha1":"fb398f3f3f24abaf4ea927291add7a67fb73000e"},{"version":"26.2.0-beta04","sha1":"6d3120b1295884a0ca647381fb49f94891b52f6"},{"version":"26.2.0-beta05","sha1":"2bfebb10a1af9a7ea54065faf43f635d066d482c"},{"version":"26.2.0-rc01","sha1":"722f31fcdfb312b3a7d575836d9f47c4488a2933"},{"version":"26.2.0-rc02","sha1":"f7bb1bff79e4447307abb8e05452ed8fc576a4d1"},{"version":"26.2.0-rc03","sha1":"4737a2483c9288fbe16159862355f2b9d058d9dd"},{"version":"26.2.0","sha1":"6d8c890bf7e44811a7e1d4c00a8635ec10f4adea"},{"version":"26.2.1","sha1":"db637f20d2c56d74a1b504d4e39a0bc8817fcaf4"},{"version":"26.3.0-alpha01","sha1":"35318a37014b63e1bcfc8d1465fbb1af229e37d1"},{"version":"26.3.0-alpha02","sha1":"f9d1c8575c33c54757885bd75a28d04db46509c6"},{"version":"26.3.0-alpha03","sha1":"6f1ca89109895ba49dd30bb375d193556a998b61"},{"version":"26.3.0-alpha04","sha1":"ed9d70a70a5a6e712e47f79ac8cefea92371aff3"},{"version":"26.3.0-alpha05","sha1":"bc0c729772cae55e1a16d29c5bb05f524c376258"},{"version":"26.3.0-alpha06","sha1":"4e82988d31ea9c05d8829dd7a87c96bdbc808fd2"},{"version":"26.3.0-alpha07","sha1":"f5c27428893317aca59cf7e58690173280c4c668"},{"version":"26.3.0-alpha08","sha1":"439abd0820611f12c171bfc6634bf97823001b0"},{"version":"26.3.0-alpha09","sha1":"466b1e63888ec2e6e62e0ca3b92e415790c7bc1f"},{"version":"26.3.0-alpha10","sha1":"fea134cc63c53dfeb59afd73e4be0c6b74e69755"},{"version":"26.3.0-alpha11","sha1":"8efd5a144d535e0d3e2b7f5d172125e62f3bbb94"},{"version":"26.3.0-alpha12","sha1":"1b2864dd67f600aa583d9670136fb2dd3b1282a0"},{"version":"26.3.0-alpha13","sha1":"f4359f20f4c4fe643524c0dea46002eec0b8f14a"},{"version":"26.3.0-beta01","sha1":"3a2901c6a122dcffe5fadb4283bed76d33e5592b"},{"version":"26.3.0-beta02","sha1":"8b0c00be82e4a6d72a941d278646092532d3f62f"},{"version":"26.3.0-beta03","sha1":"1a39d268457a211ca8769eaf28b51432fad08858"},{"version":"26.3.0-beta04","sha1":"4a07fa534d2c4986cbf99519e70486b31882c13f"},{"version":"26.3.0-rc01","sha1":"4185d1972c9d1f0a4fdaad63d275c2373188829c"},{"version":"26.3.0-rc02","sha1":"4c6f47525e1dd3a138f250af48fcd89736c781a1"},{"version":"26.3.0-rc03","sha1":"e357599cbafecb9537bf99e2d9207b52b6fa26cc"},{"version":"26.3.0","sha1":"64203ebd00b43c093e22fac6d2fc7b36053ef483"},{"version":"26.3.1","sha1":"f35a0accd93cfbb4306268ea748e1ac49920a0a8"},{"version":"26.3.2","sha1":"19e8b1cf0467136516ef877a68765c80ed802063"},{"version":"26.4.0-alpha01","sha1":"dff708e42c0cbf29a8fb0bdafaac8a89280b55da"},{"version":"26.4.0-alpha02","sha1":"feaf85153038a36a8e14ee88fc2911fba5c09129"},{"version":"26.4.0-alpha03","sha1":"726e0b7d18b21d1b1a025107755165c2b1f222c"},{"version":"26.4.0-alpha04","sha1":"7fdb381a649aa1ba5fc76607941d8b747d3b90b6"},{"version":"26.4.0-alpha05","sha1":"6f6d00f163da83e552f8c2e0645a7c1648607be5"},{"version":"26.4.0-alpha06","sha1":"57e8ef095bd208ecee188d03690923304ad8a8c8"},{"version":"26.4.0-alpha07","sha1":"bed9c69149a2d866a21745baad99847a456d338f"},{"version":"26.4.0-alpha08","sha1":"576b0ed2d2ad2dc50f47f631a8c0478819a9ff2a"},{"version":"26.4.0-alpha09","sha1":"94765a5e122ab3ca5cd0157475e8336e7d734655"},{"version":"26.4.0-alpha10","sha1":"b76aa8e4520d0b9eaaa77cbced6b040ed81adc88"},{"version":"26.4.0-beta01","sha1":"75f59ef9ac3fdfe505532c4133234edc6e85b03f"},{"version":"26.4.0-beta02","sha1":"9118590d9a4c144913608bbd4aacbf253f570228"},{"version":"26.4.0-beta03","sha1":"44f31fc0c64bb4051215b9e97bd5405aed9449d6"},{"version":"26.4.0-beta04","sha1":"31a95e2bccc9e74a4ac7c6ac5460823bade82c61"},{"version":"26.4.0-beta05","sha1":"c626be72d27719d79375a0bbfc4bc679d670bb14"},{"version":"26.4.0-rc01","sha1":"9af05675be30a3516ec40ad43af8d18a106804c2"},{"version":"26.4.0-rc02","sha1":"ccc8c47ca90efb1a8da889f33c08498a73ca0343"},{"version":"26.4.0-rc03","sha1":"5812812c264a97372b171bcf58fe4000950f59fa"},{"version":"26.4.0","sha1":"850cd51c9aa8ab2e39a2a0cb8d7e90282d4d18db"},{"version":"26.4.1","sha1":"c9bce8413c7e3961ea071dcf2fb7a11440fa29ce"},{"version":"26.4.2","sha1":"2c6731535adf93fde3dd64cbb3aaa0e4a1c3284"},{"version":"26.5.0-alpha01","sha1":"89e4e4370b323b4d317480af6721616f8e253637"},{"version":"26.5.0-alpha02","sha1":"203b3130d74a6f869ee9f1b4ef8002254937d942"},{"version":"26.5.0-alpha03","sha1":"44c4a79b4cf641c42ad432db2545d1e679bcbf03"},{"version":"26.5.0-alpha04","sha1":"f30a2c9082dda560f6b14439e5b47ca8ca7bf799"},{"version":"26.5.0-alpha05","sha1":"e04572874bb9ed470977cd5942c43f47b36ab0ee"},{"version":"26.5.0-alpha06","sha1":"52e12668e2a382fec256c8955b3987af469d2644"},{"version":"26.5.0-alpha07","sha1":"c2283686f612ebae4fefab07448dadb0a76c1cd3"},{"version":"26.5.0-alpha08","sha1":"d4b5371a33ea4bf5575dcaa7bb4d4cd1703eb03"},{"version":"26.5.0-alpha09","sha1":"b5c446c6eedeeb0098f2ff4437a93f6bdb2e6739"},{"version":"26.5.0-alpha10","sha1":"a6d74ed06ce5c487f9b152b42cea106ea283b5e8"},{"version":"26.5.0-alpha11","sha1":"c656c7b95ed698bf22c7d36d170adc051e8db03c"},{"version":"26.5.0-alpha12","sha1":"2be6332d49113ad807f47e55855f1acf282b1adc"},{"version":"26.5.0-alpha13","sha1":"23c86d0a4d23bdef0c96340d5f920362df6d4378"},{"version":"26.5.0-beta01","sha1":"f46bfe4966fe967c5e9ff059023bd6606a9acfee"},{"version":"26.5.0-beta02","sha1":"190c6845ff7a0a356f8e4b03ae4a038b2c677028"},{"version":"26.5.0-beta03","sha1":"9e095083a22e8ec4f6bdfff50582d3f9221635e3"},{"version":"26.5.0-beta04","sha1":"c4ce84d5f1435fe120029f1936175d8afb6ef861"},{"version":"26.5.0-beta05","sha1":"67aaf3815aff0bf2c5ec3245221e87be7da2d3e9"},{"version":"26.6.0-alpha01","sha1":"d06c088568597ada2d9037c7f5a321553f9ac967"},{"version":"26.6.0-alpha02","sha1":"52cbfcb49abc97f9f62caccde972cdbbfb6cecf1"},{"version":"26.6.0-alpha03","sha1":"467450db74c7fd9855359d266d478318dd1efa00"},{"version":"26.6.0-alpha04","sha1":"578469252a6009a2f01c1dfa8c2f61978fb1003d"}]},{"package":"inspector","versions":[{"version":"26.0.0-alpha1","sha1":"4733efbeebc1a0ea978bccd0728a7fa5fb5dde52"},{"version":"26.0.0-alpha2","sha1":"5e772a178ca960aef08dbef05852dd5ce842a70a"},{"version":"26.0.0-alpha3","sha1":"8be5fecab8146d36ba0a4fad039c2d4c0524be21"},{"version":"26.0.0-alpha4","sha1":"61938f701e6aaa1fffec1b345535c3a87c541b39"},{"version":"26.0.0-alpha5","sha1":"b3f10d632971fe33a842215c5e52383eeba85868"},{"version":"26.0.0-alpha6","sha1":"ea45676ac5cd6f3e54279052c12bc20be1ff7545"},{"version":"26.0.0-alpha7","sha1":"aea2ae06a2e00d7209cd5c60f8cac80f0220ce4b"},{"version":"26.0.0-alpha8","sha1":"8c2ae799e336e76fdfeb806f4278295e189a3bfd"},{"version":"26.0.0-alpha9","sha1":"b7e3e14fc5416bf66a9230786c133b2ef98f638e"},{"version":"26.0.0-beta1","sha1":"ef4e28cd33cb4feba0078fd0de27a173929387ad"},{"version":"26.0.0-beta2","sha1":"554ccec969dd31a506eea98d9e33d0a8ba6c834"},{"version":"26.0.0-beta3","sha1":"2e9d252f049bcc6e403c740c0c72e9aebfc5cec9"},{"version":"26.0.0-beta4","sha1":"539b8a0956b6b3a3062db188c1492fcacfccdf58"},{"version":"26.0.0-beta5","sha1":"a14bf0afcfb9c24ca12c6f02ba6a3c14474e8e85"},{"version":"26.0.0-beta6","sha1":"1b8d66c2cbf8016c7e5d2a3efb638a0375933f46"},{"version":"26.0.0-beta7","sha1":"ae1a45c6ab29774c457038fdd3ac76e2122099eb"},{"version":"26.0.0-rc1","sha1":"991c984167daf21392e2234ddccacb72ab027a01"},{"version":"26.0.0-rc2","sha1":"69267a40ddc8dcf0a9512a591128856921e5207"},{"version":"26.0.0","sha1":"658bf84f42707494c7d4eb826e76b07c3be38b73"},{"version":"26.0.1","sha1":"11f8204524c355ee7c5df44d8e65ab13b3b738ae"},{"version":"26.1.0-alpha01","sha1":"da2d141b3215def8d0ae5210b5f73c89e4ea1704"},{"version":"26.1.0-alpha02","sha1":"32c1f2a69be357e124d14256f2eeb809058d9238"},{"version":"26.1.0-alpha03","sha1":"c87e096a4b6aabe92e32776ce56020f2c785edfd"},{"version":"26.1.0-alpha04","sha1":"6ddf91a34cb4d7b32d8d2df53aced584907d13ad"},{"version":"26.1.0-alpha05","sha1":"8bfed05201e1a1863f7a9e728f7f41856f807870"},{"version":"26.1.0-alpha06","sha1":"9a39478abe3d094add46f8f90cf19a9634a08c9e"},{"version":"26.1.0-alpha07","sha1":"d8f5061255bb65433598c4816b67e7d5b7667c3b"},{"version":"26.1.0-alpha08","sha1":"133f8ff2c8a7fc41a46765b6855bb97b0d329fde"},{"version":"26.1.0-alpha09","sha1":"cd5c71671dd51268a3de66b0787fc3313402829d"},{"version":"26.1.0-beta1","sha1":"208d1007a3b5c89a371e1f33beca088eaf6104b9"},{"version":"26.1.0-beta2","sha1":"55d6d26cafe9075a51c59851e0bf13f41fb428df"},{"version":"26.1.0-beta3","sha1":"37ba0cca7be3a2f664dbe35c7b86481970aa8caf"},{"version":"26.1.0-beta4","sha1":"2e8fc74562672124904942ac0d696b130d7522ed"},{"version":"26.1.0-rc01","sha1":"ffe99f529d43d1ad5ad27386d839fb8375db727f"},{"version":"26.1.0-rc02","sha1":"b600c6489123ca2a9cc3065360ab84949c2b9784"},{"version":"26.1.0-rc03","sha1":"7e61bc436f077c1fc259401eb3dfd131a13f8b80"},{"version":"26.1.0","sha1":"3ee841791551d14502a56f4672be728bec26f71a"},{"version":"26.1.1","sha1":"f5509952141bd96304f6f2e22680a4755af3c2fc"},{"version":"26.1.2","sha1":"1ee7d42abb281d1ea6fc36aa4ca924b481f85254"},{"version":"26.1.3","sha1":"bb44fd8354933f233f47affb35ccc48c30ebee2a"},{"version":"26.1.4","sha1":"693f047b1cea88984f14b8a7917063ba5c5b0148"},{"version":"26.2.0-alpha01","sha1":"1b4efd35e9fe54d31f0087eab3a3e988666e5643"},{"version":"26.2.0-alpha02","sha1":"82ce2ad48316ade549945f0ea75ad46239528bf3"},{"version":"26.2.0-alpha03","sha1":"6df2c6fc42721f65a6c2b29b513cb8231741b229"},{"version":"26.2.0-alpha04","sha1":"8efe5b16f41a5a96ee40b9d0e64bb832476dbe09"},{"version":"26.2.0-alpha05","sha1":"a658d6a6b755f155319e5c9e99935c9417455f4d"},{"version":"26.2.0-alpha06","sha1":"8d1a95938f1ba1cfcb9ba5e1be42a66ac88fc543"},{"version":"26.2.0-alpha07","sha1":"f4c218f26337be050c1575416320635f405b735c"},{"version":"26.2.0-alpha08","sha1":"b250d0af7da3f269ef6c539a4b30afd61ed2e237"},{"version":"26.2.0-alpha09","sha1":"11d5fdeaf5f79aa2ae332576c79544e41f9cd2b2"},{"version":"26.2.0-alpha10","sha1":"1b92563632bccdf6ef540d90122f60cc22e078e1"},{"version":"26.2.0-alpha11","sha1":"f1f5b6cabeb2201b41198c4ccaf698ab722b26eb"},{"version":"26.2.0-alpha12","sha1":"6126627b13ad0d55b02f03f9fe0e06c91283c332"},{"version":"26.2.0-alpha13","sha1":"dee280e94a3b2c627191d439e834fbe5150e23c2"},{"version":"26.2.0-alpha14","sha1":"d22c9cd80cd44bb3ce52fe4295fc184c11241ac1"},{"version":"26.2.0-alpha15","sha1":"21a4b0f2878481a39645180c9ea28bf9f36b3385"},{"version":"26.2.0-alpha16","sha1":"1e4ef359c148d4f18535a0a23c8895671fba46d5"},{"version":"26.2.0-alpha17","sha1":"90b6b4729519b6abc30772976ac2a00ab23ceed"},{"version":"26.2.0-alpha18","sha1":"7aaa896c4a022b531c08b7b3097ff987af217945"},{"version":"26.2.0-beta01","sha1":"782cda6e6206829b561c4bcdafd65333b0351490"},{"version":"26.2.0-beta02","sha1":"f7f1bdba084b0acf52e7b69832ad247ea943193d"},{"version":"26.2.0-beta03","sha1":"d28bb97cf60c8f9e8d42e7daaea5148282886aea"},{"version":"26.2.0-beta04","sha1":"b9bdeaa6ec846bdb791fecf67e3c383b1c0e22b6"},{"version":"26.2.0-beta05","sha1":"bb8f2bed95311843453f2450c922f5d314245a0f"},{"version":"26.2.0-rc01","sha1":"28bc29a7a31443f6448ef76b1ef921bd0345e454"},{"version":"26.2.0-rc02","sha1":"55f9812db4402c0592c396210b79fd48724bae10"},{"version":"26.2.0-rc03","sha1":"3b8dae22ed6c69e784fe736b88af000729b5fb16"},{"version":"26.2.0","sha1":"ff29c1dcd758df55dfe4a4cb1a8e5f48d41a65ef"},{"version":"26.2.1","sha1":"dc0c553e483397772e1db421f41393367ee2f3d7"},{"version":"26.3.0-alpha01","sha1":"352d162a4e99e8bd054882d39fb62e18a5159a54"},{"version":"26.3.0-alpha02","sha1":"2cf4b02d47f0c3463e5a27ef0a7bc13698ada2a3"},{"version":"26.3.0-alpha03","sha1":"2b76cb9669d824e088cc7e9cfbc7f168414d5101"},{"version":"26.3.0-alpha04","sha1":"e355b94b42668b513ef6d1c6e606bcd59b2f73a9"},{"version":"26.3.0-alpha05","sha1":"b86b1a5b04c64fa6f65ce73a5daa66cdb616a971"},{"version":"26.3.0-alpha06","sha1":"eb4d9d2f37dff90fb1539c0d9009227a18f48d31"},{"version":"26.3.0-alpha07","sha1":"d227b8167b8583b1bc711da3e3e1de4d78e8f594"},{"version":"26.3.0-alpha08","sha1":"b2c3e9d3ad603633094fe52425210aac06691cb9"},{"version":"26.3.0-alpha09","sha1":"5f4199fd0c0b1237655bed7429f951d711a02bc2"},{"version":"26.3.0-alpha10","sha1":"42b0fc0cb695c008a611bb94b90fed0354a75426"},{"version":"26.3.0-alpha11","sha1":"bb31565cfb3a18e5be62c7c1bb0d2cdd1e800c6d"},{"version":"26.3.0-alpha12","sha1":"8dc1203e5f2724693a1d8097df3e87b444afc944"},{"version":"26.3.0-alpha13","sha1":"1a02acc96ba58348de781ffdb1be026706f8ee7"},{"version":"26.3.0-beta01","sha1":"670a63262c6ee4287e613df8dc5b89109949498b"},{"version":"26.3.0-beta02","sha1":"4802b10a8bb7622f94e62dd03ae1cdcd8753a55a"},{"version":"26.3.0-beta03","sha1":"303ad887c9555348659b5033318fa127c576f928"},{"version":"26.3.0-beta04","sha1":"5d4ed5c3d99f525187ed7faf6fa16791082ccd3e"},{"version":"26.3.0-rc01","sha1":"1527319456c3f8f26ddf86e1858179b40932b8a7"},{"version":"26.3.0-rc02","sha1":"7631acc4eb03fc199b757ea6057c83334132871e"},{"version":"26.3.0-rc03","sha1":"47a94269e96fcda942e9b216f6a4ab9fe969615e"},{"version":"26.3.0","sha1":"7b3309329b7366aafa2a1c63cf925da338e27ee1"},{"version":"26.3.1","sha1":"bd33416c7d99e8d882abd4eb103d64c7a689719f"},{"version":"26.3.2","sha1":"58492649ecc68392e5529c01f386e2a0a6be5914"},{"version":"26.4.0-alpha01","sha1":"de84d44e9ca1b677de0105a9847871466d2ec443"},{"version":"26.4.0-alpha02","sha1":"4b313f791a7deb0a01fdb9278b93a7db0b03fb0b"},{"version":"26.4.0-alpha03","sha1":"7ae5a2c08d038bc96a451c819ef45eb098310dfc"},{"version":"26.4.0-alpha04","sha1":"592df087f9fccf58b0a0a81d91ebe69e38c1c743"},{"version":"26.4.0-alpha05","sha1":"d0adea2cf7fdbf64002d1a1231a3634b7ff36495"},{"version":"26.4.0-alpha06","sha1":"cd14c4cdc07bce49fef6cb5e41c3070f546fa86"},{"version":"26.4.0-alpha07","sha1":"560c26acad8aeea44e3befe1ac6c194e1b395389"},{"version":"26.4.0-alpha08","sha1":"7c08b82edd01bfbfa501cad39df287b0ef602666"},{"version":"26.4.0-alpha09","sha1":"52f6e0850227126b9770232d67800a56d78d112a"},{"version":"26.4.0-alpha10","sha1":"db3abf32cb359215d2d922bdaae089a4883dfd31"},{"version":"26.4.0-beta01","sha1":"8b607de1cd72663c5f3dbe2d402ada9465ecc94e"},{"version":"26.4.0-beta02","sha1":"9321516d49a7d36c4a7b33dc0e3a3234bcf1b889"},{"version":"26.4.0-beta03","sha1":"9bfd38ec3306772d0cf82b5b23a4c2b9d36d7cf"},{"version":"26.4.0-beta04","sha1":"8993cfaf065f58a954974ab76e1a5ffae984f92f"},{"version":"26.4.0-beta05","sha1":"dd9eb45724c38c8d7ae09a6e36896f6ab971de42"},{"version":"26.4.0-rc01","sha1":"e4316e6aae6b33557aac5dbe80f48f947e0b554d"},{"version":"26.4.0-rc02","sha1":"8403da14088af23fcf7fe141e96550169eb35061"},{"version":"26.4.0-rc03","sha1":"881778d84dcf4a64fa4ac8faaf612a0e44c8862e"},{"version":"26.4.0","sha1":"2d85edec208573709b547728cbf524ca2995a943"},{"version":"26.4.1","sha1":"7885355208a877fc472dffec5651c235f3e9e6d"},{"version":"26.4.2","sha1":"8d4d57099533517e158aa3910f4f8ad28451a7d4"},{"version":"26.5.0-alpha01","sha1":"c0468ed2468910eec844d79f9abd3af1b83462e8"},{"version":"26.5.0-alpha02","sha1":"3cb801210923ba2206c5a92c5eedd1b7d5e945c6"},{"version":"26.5.0-alpha03","sha1":"277ee18b56bcc10a6b77643f9455804d687afdb4"},{"version":"26.5.0-alpha04","sha1":"ee4c298a31af97b981ac86688995b793d3363d86"},{"version":"26.5.0-alpha05","sha1":"73444c323d54b732f62c6be2b8b01c46033242c0"},{"version":"26.5.0-alpha06","sha1":"62ce8e9b8e4a262ec2b34b1dd59bfe6cfcb6e5c8"},{"version":"26.5.0-alpha07","sha1":"f8f081aa66f107501acb5e671a44ec4f53a3cd79"},{"version":"26.5.0-alpha08","sha1":"991f8f1225a106d1a73b61e66a9b32e5976ed8c6"},{"version":"26.5.0-alpha09","sha1":"9080b6adb7aa35db298cab2166120fd868eb1c5"},{"version":"26.5.0-alpha10","sha1":"549bebde6bbb9533c126d9a4664838b00ad4bb05"},{"version":"26.5.0-alpha11","sha1":"cce08adbc114a0919bea0fe9c27a1290fc048c8f"},{"version":"26.5.0-alpha12","sha1":"3e36e684f9316c2b263aede054041a52b0c88356"},{"version":"26.5.0-alpha13","sha1":"e0bb1bbaf54e1267fa47b6ee4dbd1ad900177aff"},{"version":"26.5.0-beta01","sha1":"83bda30aa4f1fb738e4820537819f5001c4b960b"},{"version":"26.5.0-beta02","sha1":"1de68eab58419a12de8f49415ccf7d2ca1cfcc1"},{"version":"26.5.0-beta03","sha1":"1690d5eb7d818067106dbced609bf94be5c8758e"},{"version":"26.5.0-beta04","sha1":"3942d6c7d417455eb3e3f8eae90e5fb6ee1873b7"},{"version":"26.5.0-beta05","sha1":"e4e76d60657412dbb848378de5986090807f7c4d"},{"version":"26.6.0-alpha01","sha1":"7d7c0d37f0af0480a94152b70d5dc20c9f44ee18"},{"version":"26.6.0-alpha02","sha1":"1578f9da431f0e25a95d63f4724a4a65567989de"},{"version":"26.6.0-alpha03","sha1":"d12029b734a5dca8c6f1cf5f7c6613c34a299cd4"},{"version":"26.6.0-alpha04","sha1":"1ebd72bfd606e88279440e3b573ccd31b1986abc"}]},{"package":"shared","versions":[{"version":"26.0.0-alpha1","sha1":"ec8b39b89b51a2ca3b04232fb5f9af64ffd05fc2"},{"version":"26.0.0-alpha2","sha1":"e6c75b983770ff97fa144b2be7cfeaa98eb621ff"},{"version":"26.0.0-alpha3","sha1":"5d7287075659bf5d0b0b2ebba5d9afa9e5f6961b"},{"version":"26.0.0-alpha4","sha1":"fe7b9dd679c7f4541fe756b4b4d60450f39d7812"},{"version":"26.0.0-alpha5","sha1":"3e01865ae1b536ac61fb1c1205c91320cb65e594"},{"version":"26.0.0-alpha6","sha1":"d809c5fece43b6f01813003354bbf2c0434a5585"},{"version":"26.0.0-alpha7","sha1":"6b7e7cf6fc204d9ff7b1d596f701416f890c9ead"},{"version":"26.0.0-alpha8","sha1":"6657b206192b04acde9e79d16bf89108bb51c5b4"},{"version":"26.0.0-alpha9","sha1":"da71f147127e01b69fb0185a046942359bdd8e3b"},{"version":"26.0.0-beta1","sha1":"5954d5c17b51ec58879fc717f5913654ba2bc402"},{"version":"26.0.0-beta2","sha1":"7a1da3a15b66724d45879761c0ea546367510462"},{"version":"26.0.0-beta3","sha1":"265381b5f61dd974b05c1d1079b4c410474423a0"},{"version":"26.0.0-beta4","sha1":"8606cbb41ef57a8a7786c3696a4bb116fa7d9c43"},{"version":"26.0.0-beta5","sha1":"26efc04a2c20721d462c9fc16c2709ee3115182a"},{"version":"26.0.0-beta6","sha1":"7b95ef18e5a8249c5de0e43ba13c78cd2f126e32"},{"version":"26.0.0-beta7","sha1":"179156c939724d1245db7fe482a613614674e4c9"},{"version":"26.0.0-rc1","sha1":"30253bc6c1c485766106239b248b21c5bfc441b8"},{"version":"26.0.0-rc2","sha1":"6509dbe98a31b8458990832d25afa8fe80873870"},{"version":"26.0.0","sha1":"bf1943bf2ff33b50f8f18e25353cd82d70ab24ba"},{"version":"26.0.1","sha1":"5cfc3811c2fd6cddfce2ec1bb5fbbec87e0fc462"},{"version":"26.1.0-alpha01","sha1":"6c50d9030791e4cbe829e2400093483fb52fbc2c"},{"version":"26.1.0-alpha02","sha1":"597391ffe17679d6830d8cb5a0235722edc0338c"},{"version":"26.1.0-alpha03","sha1":"a3d8e15cfbbd1a954f6d010ab331dd9533b80eef"},{"version":"26.1.0-alpha04","sha1":"e9408edb5160a40fc83809f4bc2efee587932672"},{"version":"26.1.0-alpha05","sha1":"74287bfc130f5dd41e049343d50b5535de70f8af"},{"version":"26.1.0-alpha06","sha1":"de43e15e087e435ce35d01e09b743111fe188dde"},{"version":"26.1.0-alpha07","sha1":"5d2987479a223f4c9dcf9e57c0ee80055a0721d7"},{"version":"26.1.0-alpha08","sha1":"b19edc8263caa6b268062b76e8db7fc477533ecc"},{"version":"26.1.0-alpha09","sha1":"cacd8111b836a2a4a1efc5c973a7a8547179052c"},{"version":"26.1.0-beta1","sha1":"19ee563d57b710e6ae76ce9e3ac266b72c7edfc3"},{"version":"26.1.0-beta2","sha1":"1c992eb8881b60f06accf206685d485f2266a694"},{"version":"26.1.0-beta3","sha1":"3f843e4bd14fc99fbe2a33b850806461e464b75d"},{"version":"26.1.0-beta4","sha1":"109aa0286e5e17537938cc88f0c61eb39b220c13"},{"version":"26.1.0-rc01","sha1":"4c137fc42855282bd4afd6e5a2a61d76c938e5ae"},{"version":"26.1.0-rc02","sha1":"6b983fca87556dd3d5bfb2a6d727fa148acacd6a"},{"version":"26.1.0-rc03","sha1":"83ed9636521a3871fcd383d25ac61540c007cee5"},{"version":"26.1.0","sha1":"a67c088b2bccf01342d4258f98259b39156e7803"},{"version":"26.1.1","sha1":"edf64ba5b27118f57f227471a838706c3879cf60"},{"version":"26.1.2","sha1":"bc21fe64fdaa64e59672e7d546d373f430e7557c"},{"version":"26.1.3","sha1":"fec103fe689f4c3c2cc3023fd7f25d4a5b5e2a2"},{"version":"26.1.4","sha1":"2a285c67f4b421145e47849d4114f7534707156"},{"version":"26.2.0-alpha01","sha1":"897fe3a5a066fecd0473fd28e1bf87d89a8bd1f1"},{"version":"26.2.0-alpha02","sha1":"51426c7e898306b4c2630d75ef36851c301cc016"},{"version":"26.2.0-alpha03","sha1":"33ce882c37c9a5d2d49d2acc3a5daa81d8dff072"},{"version":"26.2.0-alpha04","sha1":"bf9f613fa217c49425a6d9b83558beda829a2da3"},{"version":"26.2.0-alpha05","sha1":"ccff5373254dc175252657867a81de9fc2c10693"},{"version":"26.2.0-alpha06","sha1":"734999c8003024e71d8179d1b859dd4cb8c23920"},{"version":"26.2.0-alpha07","sha1":"35964d0b3d6fcee60733cdc44f700ef803aabd3f"},{"version":"26.2.0-alpha08","sha1":"c334f660599295a39c01ed6801d8aebe32ea19c6"},{"version":"26.2.0-alpha09","sha1":"e82b8c6d7d927d1461b624944d70fa2a20bf575a"},{"version":"26.2.0-alpha10","sha1":"a3c56ef2a75a404a5864419e5a9e3aca71f633a3"},{"version":"26.2.0-alpha11","sha1":"3ff9da23284f0027d39b96edf7e1d564a5a0f5f5"},{"version":"26.2.0-alpha12","sha1":"cc806ef478f325d6a2e74c44b67ac7f03cfc072e"},{"version":"26.2.0-alpha13","sha1":"6ccb3a313a69269bf1eb30372b254cee37c6b916"},{"version":"26.2.0-alpha14","sha1":"d9610fbc7be668e79f5f5772d4c0347285ece5ae"},{"version":"26.2.0-alpha15","sha1":"e46f1aab018995657c7aa98aea50b4e4525b9356"},{"version":"26.2.0-alpha16","sha1":"d2ba012de9324aa38f5aa07488ed8d864e77e99"},{"version":"26.2.0-alpha17","sha1":"1db3d20bc70d2dfbfa9bc2bea0b3661d4b7fd889"},{"version":"26.2.0-alpha18","sha1":"51888bddc0ba406024c8f74e82669b3002c6e9ac"},{"version":"26.2.0-beta01","sha1":"e5b56f7b3c5ad3aab097a7163c265f20ba48a158"},{"version":"26.2.0-beta02","sha1":"a7a9442ec7919ff66f915a1c8cac9f8ea505d546"},{"version":"26.2.0-beta03","sha1":"c936a782200fa0ed4c92d1f97f3873c99c3a6f6"},{"version":"26.2.0-beta04","sha1":"5609b203edd914cdd09941969c328fcaac5b73d0"},{"version":"26.2.0-beta05","sha1":"2d168e2f8182323f6964f4bd89031093d59e350d"},{"version":"26.2.0-rc01","sha1":"19e3f62ca7faa3a80082abd145b531f27bfa8d47"},{"version":"26.2.0-rc02","sha1":"7c05ce0f1fbfb3333736f7e5719b93f5e2b4fde9"},{"version":"26.2.0-rc03","sha1":"2578a642cfa797e91ea3018ac8fd2bd0ffc148d8"},{"version":"26.2.0","sha1":"7501aed58595690ce73e65123b94a79e0d5ebfdf"},{"version":"26.2.1","sha1":"2e79154b4a5d2e6c1816811db6ab00cf41886737"},{"version":"26.3.0-alpha01","sha1":"489faa5132905c5620fe7f606db5502f5cfa2a55"},{"version":"26.3.0-alpha02","sha1":"7e39e78d85eee6902ef950a1039126f9ae74bbdf"},{"version":"26.3.0-alpha03","sha1":"1db1ea52e9799ba33b05a118787286aaed359e7a"},{"version":"26.3.0-alpha04","sha1":"31c157b35baddaf74f6da4269a811dd7ccedcb30"},{"version":"26.3.0-alpha05","sha1":"d47a28458be21186ca4a7a330fde42f7c92a43e1"},{"version":"26.3.0-alpha06","sha1":"42dfe9c1d986b21f956d1f58221004f7139f7c63"},{"version":"26.3.0-alpha07","sha1":"aa1fa2de38d266c20149e027869505d80920e968"},{"version":"26.3.0-alpha08","sha1":"a434d22c59794b63283473edb982041b19a2f275"},{"version":"26.3.0-alpha09","sha1":"8fa5922ff7e9be1c93d84fec94f2d09ab86d395f"},{"version":"26.3.0-alpha10","sha1":"ad2dc359cc52bfe83672c0d946c7fe7a0d16b85f"},{"version":"26.3.0-alpha11","sha1":"780b80e78d079eaa3e1cc1b33f11bd2aa7146fcf"},{"version":"26.3.0-alpha12","sha1":"14d5733e51af5a2a585483425a94cbd144f5cbe2"},{"version":"26.3.0-alpha13","sha1":"fd137522881561c4e287574f887a1969dcc1943b"},{"version":"26.3.0-beta01","sha1":"400cccb02e055f1b63ecd8093e74473ee502321d"},{"version":"26.3.0-beta02","sha1":"54d1731d35a8dfbb364ad3083c9b3dfac744df4c"},{"version":"26.3.0-beta03","sha1":"fce1bbf755250ffbcda2a3307ce1b1f45b828f8"},{"version":"26.3.0-beta04","sha1":"d6b40718c6ea5cf3a41f0d627e5d084e4136ad31"},{"version":"26.3.0-rc01","sha1":"a5a58fd4e406f7cbfc72ee05452afdf9b698589e"},{"version":"26.3.0-rc02","sha1":"9eaf6f10f0deedb533dd718368f98432e83b512c"},{"version":"26.3.0-rc03","sha1":"df251af6b26277a99d02b3fe283a7a2747bc077b"},{"version":"26.3.0","sha1":"12dc55cbfdcdbe731d95d706338c5741c7a0831a"},{"version":"26.3.1","sha1":"3457faee7a68d84dcbd258889567153acfc731a2"},{"version":"26.3.2","sha1":"8bc0130e2de66b105906c367cbe5581770defffb"},{"version":"26.4.0-alpha01","sha1":"ebf53b37ef01d900fd912519392e5817542756b"},{"version":"26.4.0-alpha02","sha1":"b4c4ef7b9ebf571c5d92a6af7825a0454b299461"},{"version":"26.4.0-alpha03","sha1":"31d688df24d1f263b22d183442f6b2b043797329"},{"version":"26.4.0-alpha04","sha1":"59ff4aa82c6e5144f90ba9f61a76576e70825b32"},{"version":"26.4.0-alpha05","sha1":"cf560b0a83a63ae722b8f1c3bf981526075febfc"},{"version":"26.4.0-alpha06","sha1":"19267fff3ebd480b6f145e0ebd2f424c1e6f1da0"},{"version":"26.4.0-alpha07","sha1":"6f093191580a2c0c54f8d187909d1de7869c646"},{"version":"26.4.0-alpha08","sha1":"cbb21afb5997249a70ab24161709d6f24b1cefde"},{"version":"26.4.0-alpha09","sha1":"d4b281c6f8c310a929e64d0b78fdc1175d61322"},{"version":"26.4.0-alpha10","sha1":"243fd5b7f3bfec00ee5c38ddf3f48d070c782e41"},{"version":"26.4.0-beta01","sha1":"9f2e2cac5c7b7e798e082ad80cd45e8fcdac4042"},{"version":"26.4.0-beta02","sha1":"beff2213cb5605e02f69576431a84a0d8024967"},{"version":"26.4.0-beta03","sha1":"ac1275bed485aeacc60283693c75a218e1ee42be"},{"version":"26.4.0-beta04","sha1":"f376284c290582afe4a725e59d0d07eb81b750d8"},{"version":"26.4.0-beta05","sha1":"5116955b2f662d359dfce848e1c8c6b13cc0ecbf"},{"version":"26.4.0-rc01","sha1":"9bb75e97b62b7d32d75355e1b4f921909d87c91e"},{"version":"26.4.0-rc02","sha1":"64b8236e8678c7673f763e42fda514e5925a152b"},{"version":"26.4.0-rc03","sha1":"7b3e5769029f1c6d8ee78434728d027799dbd064"},{"version":"26.4.0","sha1":"2d083560f501898bcd0c42e704abcfb7746ef20f"},{"version":"26.4.1","sha1":"8b6f40928c3aecc2e08125a2e3baf3c2228244a2"},{"version":"26.4.2","sha1":"d2a8af3af9494a818ea72987db90dc9e3b26359c"},{"version":"26.5.0-alpha01","sha1":"879c878a990b9fc5c32a8703bc6901401bc7794e"},{"version":"26.5.0-alpha02","sha1":"a8717928a31cd5d234898430c8ebcae2b14e52ad"},{"version":"26.5.0-alpha03","sha1":"9c6c5044d180f08995385dba8d83e8f986a623c1"},{"version":"26.5.0-alpha04","sha1":"14e089cee1ac7418eeb9fb6bcda42e548653d3d1"},{"version":"26.5.0-alpha05","sha1":"d04e62d9f4b4833ac21db1ca7b745fed2cfcc2f1"},{"version":"26.5.0-alpha06","sha1":"b610ab164e67397d337ea80b3ca7c5a853a31b36"},{"version":"26.5.0-alpha07","sha1":"2816534e78a8fb810a271fb2814d1ec4e9537cbd"},{"version":"26.5.0-alpha08","sha1":"abf99b94fc8a80d87218f7d8d061c322ed573248"},{"version":"26.5.0-alpha09","sha1":"b88e32d3aa5d564417be66d16d487a6c64c1b7b1"},{"version":"26.5.0-alpha10","sha1":"5ba379f193d2cf6feaf59623fef6d415437bdcb2"},{"version":"26.5.0-alpha11","sha1":"6a2b721354e17c27f29cb99de867a69eb8365984"},{"version":"26.5.0-alpha12","sha1":"c576ec787e626b3452390698aa8bae11526b9d18"},{"version":"26.5.0-alpha13","sha1":"1b467befe220cd3288c1aeb2b64820455d2f5a62"},{"version":"26.5.0-beta01","sha1":"ff378731ad1b5aeb81d5745be4b22dddd5ca6515"},{"version":"26.5.0-beta02","sha1":"f3a28447b6ee55267ccc55021e848c19392c301"},{"version":"26.5.0-beta03","sha1":"111d273031020da6d4469a24b59851d29e1045c0"},{"version":"26.5.0-beta04","sha1":"b6c0390b6c93d42bbb347ec1850e4241c84f3df6"},{"version":"26.5.0-beta05","sha1":"1df6fa3541c644a5b8bca0f8a679ba1b26b4c366"},{"version":"26.6.0-alpha01","sha1":"1d8e95f7d82fa54840183a95382ec6280bd8fa79"},{"version":"26.6.0-alpha02","sha1":"29478e1e59f2d2006f230286741014c6f7f51d2d"},{"version":"26.6.0-alpha03","sha1":"c3a731215730aeb95fe2f7930ad919e4208f1ff2"},{"version":"26.6.0-alpha04","sha1":"7a3890b9994421a5c51940f81e07d7c3e3f30b3a"}]},{"package":"publisher","versions":[{"version":"26.0.0-alpha1","sha1":"8dbdcb58b341be14c7ea446f8c4bfa179dd85e6b"},{"version":"26.0.0-alpha2","sha1":"4ac2cd551b4c0bcc5a4900a58538ed3e8009305d"},{"version":"26.0.0-alpha3","sha1":"6e58c7dfed7ac32d9f67f52f2f46ce2ba1c9c9a"},{"version":"26.0.0-alpha4","sha1":"f9f2e7be53ee43b7594c244b626008fa1e9d657d"},{"version":"26.0.0-alpha5","sha1":"e16a596397bac54ff1b3dbf07b8adbf305ca7744"},{"version":"26.0.0-alpha6","sha1":"6f2fab6260aad8b0169c4d9b047407cfc02f6893"},{"version":"26.0.0-alpha7","sha1":"e0045ef0b9a9b793c4bff09c2d6b074a26f5ce7e"},{"version":"26.0.0-alpha8","sha1":"28c1565821271d02603ea6c0c345d7bd88ddcf38"},{"version":"26.0.0-alpha9","sha1":"1dce0b0c4994a7aff61228eab5797b7ac07fc470"},{"version":"26.0.0-beta1","sha1":"f5d05e8e085300c81a67a56c263eb5c844100ed"},{"version":"26.0.0-beta2","sha1":"c25bac537560612aaef5bc7b1f79548a73239557"},{"version":"26.0.0-beta3","sha1":"543b5d8bf21931cff302a979cd73e7f5c66e6271"},{"version":"26.0.0-beta4","sha1":"edae5a3da4711739f3498c064e136d16afec143e"},{"version":"26.0.0-beta5","sha1":"3e7e69625313ba32abbcc659a616f0bec5ef7510"},{"version":"26.0.0-beta6","sha1":"687be9108df05a8cb58f2278088c78590a0e0012"},{"version":"26.0.0-beta7","sha1":"dd425c664f92f06d1ca08ace8ab9b80e7fcd4891"},{"version":"26.0.0-rc1","sha1":"13bed19812ff80fb41ab6825c27dbcee3d5b9a4d"},{"version":"26.0.0-rc2","sha1":"f5b855c2efa0e478cf7f95817a42ac9bb22184d2"},{"version":"26.0.0","sha1":"98e5484f218d7b84c1623b7ab616968cb3fab1d3"},{"version":"26.0.1","sha1":"9f5a9912b6834f247caa0ccba6c6d4f08a8d046a"},{"version":"26.1.0-alpha01","sha1":"724d826f2e5278b23e342e9c7c44ed6af5426998"},{"version":"26.1.0-alpha02","sha1":"46b6290a5964d562a3f673416aec7cd7c53ff1f"},{"version":"26.1.0-alpha03","sha1":"9798bde620d9432223060808f63af077ff5c337e"},{"version":"26.1.0-alpha04","sha1":"78a12444b3bc15bf4e41f3fc213a297bf3d4f5c2"},{"version":"26.1.0-alpha05","sha1":"8b80a4b87d8996c9e9ecaba1079bc3cb249dcb20"},{"version":"26.1.0-alpha06","sha1":"952c5f8957a047d3417d12bf808d7a79200bef2e"},{"version":"26.1.0-alpha07","sha1":"c15e90b2cadc270ef66ba41a2d41d06ddbc7451"},{"version":"26.1.0-alpha08","sha1":"ed905e54a15a7f0353fdbb401d89028a56df8a05"},{"version":"26.1.0-alpha09","sha1":"c6a679cba763f464a283f4f5aad4c933fceae462"},{"version":"26.1.0-beta1","sha1":"ba3d0b8f2a1e3721cab211def7a0d1a9c0da8204"},{"version":"26.1.0-beta2","sha1":"5bc5479e1c9c3371b0082ff1d9d28caed7e08e7"},{"version":"26.1.0-beta3","sha1":"da3abe20f679dff6491e6193dd8943de744dd50b"},{"version":"26.1.0-beta4","sha1":"928f4182820364f71f7b760c1c5d41b607da9946"},{"version":"26.1.0-rc01","sha1":"18021934a50f0ca64d888b8abdd1525414bfa459"},{"version":"26.1.0-rc02","sha1":"165c35fc907450d4a9a59cf0f98359a908c58bc8"},{"version":"26.1.0-rc03","sha1":"8443414da467d589651e0fca9e11fca38e7370d1"},{"version":"26.1.0","sha1":"2c11ea1afb3bb65e46633fb6c0d98c180d79f1db"},{"version":"26.1.1","sha1":"459cd89aca816d8dfc67df813194ff7cfe08c3fe"},{"version":"26.1.2","sha1":"6bef8c4aa7aa5e7fca36a93f1049b988e105ba02"},{"version":"26.1.3","sha1":"acabd59e134c21e526da34c7e374df045cea3c6"},{"version":"26.1.4","sha1":"8bfa0ab58ab074ffd06a99e0fe07763a7a381de5"},{"version":"26.2.0-alpha01","sha1":"ea174a2e8777cd84e3e0375ef1a1aa8385dae72b"},{"version":"26.2.0-alpha02","sha1":"b273ef8770b2c2119ae474a7c04b74e5e71f218c"},{"version":"26.2.0-alpha03","sha1":"411f9800f60e771d7709806717ac1fc70082c515"},{"version":"26.2.0-alpha04","sha1":"13896784fbe2b057e17d37457bae60030b388fee"},{"version":"26.2.0-alpha05","sha1":"1b37678711756567163485204d9cadef5e57ace5"},{"version":"26.2.0-alpha06","sha1":"e190df6870bceeb4f275c9a05f422238c05f2c0c"},{"version":"26.2.0-alpha07","sha1":"17d0affffe29333b6ebd32308c77ab5cb6c6f5db"},{"version":"26.2.0-alpha08","sha1":"453043a1be642d4578239d61d400d0427aa35768"},{"version":"26.2.0-alpha09","sha1":"599be4454757b630c30bebcf089b9d7442029e07"},{"version":"26.2.0-alpha10","sha1":"5e699c079fd173a53efe93c0cc3cee2e1d8de544"},{"version":"26.2.0-alpha11","sha1":"b216db560452c58068e43fbdca87267e784f80cc"},{"version":"26.2.0-alpha12","sha1":"10d0aa4f0ebd0a4223e8b3b5002c7fc1f14c1c6a"},{"version":"26.2.0-alpha13","sha1":"7ffbe25c73229ca905276b0c0ff37fbb904c5d26"},{"version":"26.2.0-alpha14","sha1":"16089e056080991587b2d335d5931e524836547"},{"version":"26.2.0-alpha15","sha1":"49ad6e435a453129916ca8e8cab0b60f2923961"},{"version":"26.2.0-alpha16","sha1":"40aec4675529b7da5d353ea2433a802762b07c30"},{"version":"26.2.0-alpha17","sha1":"913aa61e48eea80a0b3ac0eb23afb23b90c1892e"},{"version":"26.2.0-alpha18","sha1":"6ee65f62b70c80dbb7cb4bfbf202ccf462875da9"},{"version":"26.2.0-beta01","sha1":"76e142c7eed357057cd5eca5a0b0ff7e39a5d217"},{"version":"26.2.0-beta02","sha1":"fd8dd3ab4d95c03d5efe6e3c9f09755cffd7f1af"},{"version":"26.2.0-beta03","sha1":"b7ffd78e63b54da3d388ed7f0ab2ded054ee8c"},{"version":"26.2.0-beta04","sha1":"c446db4cc1c008dc88f3c2c03b1f011ebca6656c"},{"version":"26.2.0-beta05","sha1":"e1e567d95c476cd9ff3f9d84c5d978153f60f7a4"},{"version":"26.2.0-rc01","sha1":"7343aad967928ae69b60351438ce3d4e47aebf11"},{"version":"26.2.0-rc02","sha1":"6104880e7dfb971d68415f280410c5fb48d80221"},{"version":"26.2.0-rc03","sha1":"846bdd58f229c2d903b460ead684c722afd3ccc"},{"version":"26.2.0","sha1":"35874fdc8501a3dcde95d6024e65b6e05b921831"},{"version":"26.2.1","sha1":"277bfc941f19991e0e6b713df26d004125bc951e"},{"version":"26.3.0-alpha01","sha1":"8a71ee3ef89715c79ffbe2c3a842edb3eba5a67b"},{"version":"26.3.0-alpha02","sha1":"8714399f532c3171b8f95c0904d8a168bd20a651"},{"version":"26.3.0-alpha03","sha1":"f3c3c53d1fe9d98053f2d8f6219d5c8dd10f25cc"},{"version":"26.3.0-alpha04","sha1":"95c4f022ecab97d85243959e9d5d563cd1063313"},{"version":"26.3.0-alpha05","sha1":"4ed6e90c5aabf94b6401e0fadfa9f51fed08a86f"},{"version":"26.3.0-alpha06","sha1":"771115b30f9f195bcd0881214061305d82927fce"},{"version":"26.3.0-alpha07","sha1":"92eab00d4db36866e3819eed7d46f62612009a79"},{"version":"26.3.0-alpha08","sha1":"422239043d5d9abb436757f8242fef64d46edaef"},{"version":"26.3.0-alpha09","sha1":"de23e7a65feaad8f0d9561c7e5aaa630b8558629"},{"version":"26.3.0-alpha10","sha1":"81e852595fb5ea637a6251df09d5c92efe9bc25f"},{"version":"26.3.0-alpha11","sha1":"84a66a6224a262b3b2c08cf6e6b48f9c766f6ae4"},{"version":"26.3.0-alpha12","sha1":"49b5e282cd428c4a3ef79cc19745e957b6223422"},{"version":"26.3.0-alpha13","sha1":"84e2d1c70c8eba074b72a02d084deee2921f33d5"},{"version":"26.3.0-beta01","sha1":"5f1cecc924e7d18ebdac89d1e30d192525ad2847"},{"version":"26.3.0-beta02","sha1":"b0665cf160b2f6e184135700b4436377973fd3d1"},{"version":"26.3.0-beta03","sha1":"f5c0f9fb98a59638e088f3947e85443e689b9472"},{"version":"26.3.0-beta04","sha1":"7b86fdc69719da4a230ff2bec3877c65fc836d49"},{"version":"26.3.0-rc01","sha1":"8b9b7f5d7fd571d4fb8703e60900d141ca40ef81"},{"version":"26.3.0-rc02","sha1":"bf93fd3b2cfd833d28c0bc4161a35d4c1f0f3f66"},{"version":"26.3.0-rc03","sha1":"9e34251b8623ca0f2219dfc920ede96b7eeb32a7"},{"version":"26.3.0","sha1":"f1adb29adbc975713fbba751daa4bed0fceec037"},{"version":"26.3.1","sha1":"22784037cf93e42723b679551c47e454580e6aee"},{"version":"26.3.2","sha1":"396b9094596aa0aef5ffb0057ea4024c58f83761"},{"version":"26.4.0-alpha01","sha1":"71bfeef8578c8ae7db3ba31bd09f058e808a75d3"},{"version":"26.4.0-alpha02","sha1":"601acf41ee882451ccd2b6950ea8d8f0dab997d2"},{"version":"26.4.0-alpha03","sha1":"4ea0f65e95ae64dffa52115eca11aa934856e81b"},{"version":"26.4.0-alpha04","sha1":"e9cc54c87eff00160fdc4528c728d00d41714311"},{"version":"26.4.0-alpha05","sha1":"8f50dcea36dee6a0b96e07f9e8c0fe2774bbc92a"},{"version":"26.4.0-alpha06","sha1":"c2e2ebc48f77fc9d0c62bf85ab57e4bbb393b9e3"},{"version":"26.4.0-alpha07","sha1":"8e43bb11e992ec28929776091e8101032806afe7"},{"version":"26.4.0-alpha08","sha1":"f64a45190d7151b83788f6d0a351c6e20dd41a8d"},{"version":"26.4.0-alpha09","sha1":"c2f6898ab94bb491e882738f2307930f66bf48b1"},{"version":"26.4.0-alpha10","sha1":"b8a3fb8a965553a6246ec1c8878a232afd956dad"},{"version":"26.4.0-beta01","sha1":"686f4164c52707c50497448f2ebc5c4e1eda52d8"},{"version":"26.4.0-beta02","sha1":"cc65444936ec667d78f9b697af7a3eeae6c5f434"},{"version":"26.4.0-beta03","sha1":"3a2cd408d10aad515be7b3aec51b3180ba59670a"},{"version":"26.4.0-beta04","sha1":"3b3363a3cc4e87902aa5dbab236776f9d1efc292"},{"version":"26.4.0-beta05","sha1":"36b446e48d8cabb8dc448347ac7c089db827b73c"},{"version":"26.4.0-rc01","sha1":"122e6405ebbd0c4b3d69a3ec2eab5e59f08ac0c2"},{"version":"26.4.0-rc02","sha1":"dd04b99e1a263386e13c2760412ee69107f5ec85"},{"version":"26.4.0-rc03","sha1":"7f386b1a57be55078dc45544d7bf6e8ed77185cf"},{"version":"26.4.0","sha1":"8f1959e9bb0a97564ecf08dcc7c942a81dd6247c"},{"version":"26.4.1","sha1":"c22908c4a3daab3d3e738a34d3f23fcfd12e0bff"},{"version":"26.4.2","sha1":"add0b679efcc596d5ea501edf2d719c5eeaed99b"},{"version":"26.5.0-alpha01","sha1":"725fbc6435740c0f93aee71fd0e2949b1789acbf"},{"version":"26.5.0-alpha02","sha1":"f31bf572c8f2aba4eb2dada6bd81271c1b18a57"},{"version":"26.5.0-alpha03","sha1":"e2c65f6c76c6c237f0fcee37cccd3fa30a9f24ad"},{"version":"26.5.0-alpha04","sha1":"354ac60c127fb70d6d65ecf7e3b00cc2d1b1780d"},{"version":"26.5.0-alpha05","sha1":"5f1c9655f0e8d6bafaf10b6780e7f9637b4ac700"},{"version":"26.5.0-alpha06","sha1":"4e4c1dec9b9384c724dd1a7f5f9a980a10e711e8"},{"version":"26.5.0-alpha07","sha1":"201eb6f083de03b4366baa758b87379b42ef9f98"},{"version":"26.5.0-alpha08","sha1":"b7b01fbf3af1d5d25d8b19be101bba3950e7f663"},{"version":"26.5.0-alpha09","sha1":"e1dfdd6e77da0d0eef2e2c96ccbc527135e243f4"},{"version":"26.5.0-alpha10","sha1":"89e60ae92bc5107cfa625cc5f4b5ab5000e6cd0d"},{"version":"26.5.0-alpha11","sha1":"1ad5bf28e61c25f3de685050bf2f46955ce1157a"},{"version":"26.5.0-alpha12","sha1":"ad39a3751895ac8ede1e0830fdeabde0b450ab68"},{"version":"26.5.0-alpha13","sha1":"78db0fe9fd3c5abefe6f43e605cea342fe7050d8"},{"version":"26.5.0-beta01","sha1":"2f1dffef8c1c00613e865a65374f7c5f26b78dbb"},{"version":"26.5.0-beta02","sha1":"728af83f878ca6ee75978db3e895ed3c6311e340"},{"version":"26.5.0-beta03","sha1":"57ddda6beaaac616659ee95eeb892684733d610d"},{"version":"26.5.0-beta04","sha1":"6b25596da909cc66320f99cf291d2bd92b21190b"},{"version":"26.5.0-beta05","sha1":"d0eb2993fc26ad17d728a9a84887c3274e56bdf"},{"version":"26.6.0-alpha01","sha1":"ab4bd2a6b6072f4533c530e1619b6a33daf69aaf"},{"version":"26.6.0-alpha02","sha1":"3c843aa4fcce59fbfdcc2166fcd098c7f12d2832"},{"version":"26.6.0-alpha03","sha1":"f58030b8a350b62a07693ba991e4682c4a2a7520"},{"version":"26.6.0-alpha04","sha1":"2b95fa23ea38afa9de90a06ab724b32da612535e"}]},{"package":"crash","versions":[{"version":"26.2.0-alpha16","sha1":"a91f84777ee3a10fb886063a5bc24e5abdb8e7c8"},{"version":"26.2.0-alpha17","sha1":"3ecda214d2cd04b5fa40ea9e607db6512c89177d"},{"version":"26.2.0-alpha18","sha1":"67f21223a85bec96c5e80cae8ebdc6e49a48702c"},{"version":"26.2.0-beta01","sha1":"399433a34c32a5fe27e469d4e0a3e62d25018a4d"},{"version":"26.2.0-beta02","sha1":"d807e0e156ffed077a9249a08b98d059ecf40e9f"},{"version":"26.2.0-beta03","sha1":"bb43fba93239b345b7e82efcd6c5345d2820417d"},{"version":"26.2.0-beta04","sha1":"f68a4c131fb47066c9a07d8fe82da43b775c7996"},{"version":"26.2.0-beta05","sha1":"6f9632e7ed3f4689712cf242d8fc368127f17652"},{"version":"26.2.0-rc01","sha1":"d084cdf620e04d5d15b9e13f6bf5eec4e6705f3c"},{"version":"26.2.0-rc02","sha1":"31cc53fca388ba96ee2d7ebc71402ea02b289efc"},{"version":"26.2.0-rc03","sha1":"2abe5fdc39d5b5b4b2b93b06ca6049a28e475f94"},{"version":"26.2.0","sha1":"d8e753949109b698f06c61ba98c475f807e68d1a"},{"version":"26.2.1","sha1":"ca7e89eebc7c27af8f7aea2ec070873fcd90602a"},{"version":"26.3.0-alpha01","sha1":"d191c36a8405a455c29c7cfb49581d21382846a8"},{"version":"26.3.0-alpha02","sha1":"7f9b7472855e0c6b21798171646a85549039d419"},{"version":"26.3.0-alpha03","sha1":"4facaa056c3048bded3e5d1801c6935ca0ea6842"},{"version":"26.3.0-alpha04","sha1":"753fafa49f6275fda65b8a4b9474faa0d67ef4ca"},{"version":"26.3.0-alpha05","sha1":"b942c66f432787a2db6454649996947ad6f951b8"},{"version":"26.3.0-alpha06","sha1":"ddfa70b230aae76d1686ac9fc3878863a540e7fd"},{"version":"26.3.0-alpha07","sha1":"2db044c9019658ea30ca16128b68422b6c13c753"},{"version":"26.3.0-alpha08","sha1":"5168b1d439983297db378f5043e0c1247266f60d"},{"version":"26.3.0-alpha09","sha1":"90840e5f3d8c588087d60dcf6433413969d38b4f"},{"version":"26.3.0-alpha10","sha1":"27ef9e837fee4f5cc3201ecb46d405b1d502a599"},{"version":"26.3.0-alpha11","sha1":"fb0c6b55143e0e9008f17125958bd3116a4e6fec"},{"version":"26.3.0-alpha12","sha1":"5255465cac2343d5e77b4e854419ef5eab66aa6a"},{"version":"26.3.0-alpha13","sha1":"a80c4ee94571ff2ca4d83cff70503d6b5fc00006"},{"version":"26.3.0-beta01","sha1":"c8fd916d703e786404b4f1fd72f1bee0bdf96a38"},{"version":"26.3.0-beta02","sha1":"6daf047e23b66b63f88b9a0537a115cd58faef2b"},{"version":"26.3.0-beta03","sha1":"555a7111aa805af940ed6d004ec4251210fe6b57"},{"version":"26.3.0-beta04","sha1":"794ef3cfb3ee7d858632eeb765e1df998a194957"},{"version":"26.3.0-rc01","sha1":"ae5a19e8f486dcd615ef97b8445ecfbdb7ab613b"},{"version":"26.3.0-rc02","sha1":"2d31846fbad54e4d5be7c322b1a9d354714645f"},{"version":"26.3.0-rc03","sha1":"a02599e2ac1b173091ad70312735ced1ddce65f9"},{"version":"26.3.0","sha1":"f1a8706fd6b3402d6e6c5d67b6ce7503394c1f16"},{"version":"26.3.1","sha1":"46163f2cf0e16a1ba9be2ad6dddc39f1a1d6b613"},{"version":"26.3.2","sha1":"1eb90f176daa64456942b9511e280de3f698fb40"},{"version":"26.4.0-alpha01","sha1":"30ca85b230ba8071f637407cdccfaea2e711fbd4"},{"version":"26.4.0-alpha02","sha1":"63c712011eb4f8aced1d6096f384aa28bb488a26"},{"version":"26.4.0-alpha03","sha1":"27081a14c2280657bbc9d93c6595a9c48ddeede7"},{"version":"26.4.0-alpha04","sha1":"e890ef7a1247b06a18fff86a592d6103c4310ccf"},{"version":"26.4.0-alpha05","sha1":"b290a3947a00c4cc6d760a881a2b0e05c5decae0"},{"version":"26.4.0-alpha06","sha1":"20e6e8930e5e28bbd496e790adc7738a76b205b9"},{"version":"26.4.0-alpha07","sha1":"97c2e9e73f81287d97d461b0d8feca51a89d6916"},{"version":"26.4.0-alpha08","sha1":"aa56ae2a3236f627b71f330c5d3aa2b955a044d"},{"version":"26.4.0-alpha09","sha1":"d44cf63b7d6236282c69c370207e948f9d6f0c08"},{"version":"26.4.0-alpha10","sha1":"7cc589a37bcf1de343627b8e29b9eb2776b4e68c"},{"version":"26.4.0-beta01","sha1":"32b20b6642b5b5d111851cacda0aa3faba64e045"},{"version":"26.4.0-beta02","sha1":"6b4d307dfbf372b3d7a08620cf972e4f03ad9a6c"},{"version":"26.4.0-beta03","sha1":"8944a83dccd3cebacce730a041f2780a7dea5e1d"},{"version":"26.4.0-beta04","sha1":"509e3c916e773458c74d05dda556c6fff2b50b52"},{"version":"26.4.0-beta05","sha1":"147d14cfdfd3dbef859c24606d6fd1e5fb3befe1"},{"version":"26.4.0-rc01","sha1":"1389313e1ea338275a47750b4b02f4f720c9e128"},{"version":"26.4.0-rc02","sha1":"dd946ceb632ef80bef5c5e2b512a5d2309c7c432"},{"version":"26.4.0-rc03","sha1":"d586aff2bcb0bd82ab7a65c0696877bba8adbb0f"},{"version":"26.4.0","sha1":"45f30743b0d2fb45f1e1458e56baa104221f5501"},{"version":"26.4.1","sha1":"4019df9bc90acb3abc94fcf0c790bf1df799cb77"},{"version":"26.4.2","sha1":"314eb97aab1d77586460ca9c27f8358fc55a75cf"},{"version":"26.5.0-alpha01","sha1":"54f2a6e5b5481721583acea8a29878cd18877ba2"},{"version":"26.5.0-alpha02","sha1":"ea0b53a14e35f6b597e916c9cc5272503e8dc8c4"},{"version":"26.5.0-alpha03","sha1":"fbcb3dd4fc6c17efb5adbb68cdaf8ec51c7d5ca4"},{"version":"26.5.0-alpha04","sha1":"5920afdeef05fbac6e4044f30f5cd9dc1719dd8e"},{"version":"26.5.0-alpha05","sha1":"4afdc9f7585fca9f752e2af92f5ba9b3f07ac476"},{"version":"26.5.0-alpha06","sha1":"89474e422449319cf114942778e6bcb642562d18"},{"version":"26.5.0-alpha07","sha1":"b06c0fa6e53dd5c90ef3c6ae7037849c9097d46d"},{"version":"26.5.0-alpha08","sha1":"9cff1a15264404c18a028e9ec337127f8383dc85"},{"version":"26.5.0-alpha09","sha1":"f9eafb9bf06717ddc1bc932ad3a2b2814738c7e0"},{"version":"26.5.0-alpha10","sha1":"bd78e6298998fdcb031fd0516cad8c4d9d0ff773"},{"version":"26.5.0-alpha11","sha1":"a340e0d01cccc0fa50140290ee6fd1b5d039c805"},{"version":"26.5.0-alpha12","sha1":"e12fa618487e4d447771ab47fd8379883e7df471"},{"version":"26.5.0-alpha13","sha1":"1827f44cf214b48889bd25d36ce5183088a291b6"},{"version":"26.5.0-beta01","sha1":"7fdba85bbceb885012ee13aa543d9efde69d958d"},{"version":"26.5.0-beta02","sha1":"9f96066dc51e778a0828d47594463aa56e544acd"},{"version":"26.5.0-beta03","sha1":"42ee3c335ff61703f8b7caa4711c9c058828bd0f"},{"version":"26.5.0-beta04","sha1":"a7edd545b1e5f4adf3e0c16f1a23d0b6141f349f"},{"version":"26.5.0-beta05","sha1":"3a264b9a5ead80b471be5d45d766786eb4389445"},{"version":"26.6.0-alpha01","sha1":"b281895f4a8d658339caddc6da8a44d07396bb6c"},{"version":"26.6.0-alpha02","sha1":"6f20a0c05e9bc0e050f0bc119d4cc90040a62c7c"},{"version":"26.6.0-alpha03","sha1":"b6a07411c7322cc1b24e0489591434a0ea95abd8"},{"version":"26.6.0-alpha04","sha1":"28535cc3ace33a069125900cc2a5ddac2ff8bdd2"}]},{"package":"testing","versions":[{"version":"26.3.0","sha1":"7d7404dba9eaa0e856ecf2e56ab2dd7fc68525a7"},{"version":"26.3.1","sha1":"3fedf3746a4e1c8c73411c8a571a3beb66eb9eeb"},{"version":"26.3.2","sha1":"2e1bf5ab0f23dd02988560b972746d69a112a727"},{"version":"26.4.0-alpha06","sha1":"294033f82150023f228a8c6fd4df04fe79a424a7"},{"version":"26.4.0-alpha07","sha1":"33fff8c7d59e3ed67ee53e3fd1cdf81f43adc428"},{"version":"26.4.0-alpha08","sha1":"56e87e3174ed5fde27f93a76dd4f19a9cc387bf"},{"version":"26.4.0-alpha09","sha1":"9b0e775e448e43d326721f0451a201524ca6d37"},{"version":"26.4.0-alpha10","sha1":"14f79a4771db920530a4273ba347581f1bc56d2f"},{"version":"26.4.0-beta01","sha1":"8f6d21cf6bdec54e4f61fa923569b9121869c566"},{"version":"26.4.0-beta02","sha1":"f32b881aec23f077da1a5ae85838fdd6616cb054"},{"version":"26.4.0-beta03","sha1":"d363315c7b2f0497d8e113d394705644b8f7ecb0"},{"version":"26.4.0-beta04","sha1":"5ec2a97d9cfa3ca2279f45686c814a257dbaa8bd"},{"version":"26.4.0-beta05","sha1":"80f809ebafe96e2d8d90f6ef6c54541c25e2b232"},{"version":"26.4.0-rc01","sha1":"af8bd1256af9f6ea8e5920c33fa7d0f0486c7cb8"},{"version":"26.4.0-rc02","sha1":"225434f7d5d831036c2477225d1129fd277ff5cf"},{"version":"26.4.0-rc03","sha1":"1dacbe8a292cd6240e521ce573d3d1e2208e9def"},{"version":"26.4.0","sha1":"d792cba8f736a4fabb2fbad498be707c9a3a963f"},{"version":"26.4.1","sha1":"7086c8b5a2ea2c9276a53086d8c2094ae063c2ff"},{"version":"26.4.2","sha1":"2501e669281b4f3e9d3a9f954d9f09b360ee90d4"},{"version":"26.5.0-alpha01","sha1":"3ccf8357afa211316d0d21dff7626d0ff4ad6563"},{"version":"26.5.0-alpha02","sha1":"35f54cb92995b1fb89037532269acb4b7e855669"},{"version":"26.5.0-alpha03","sha1":"383270410cbd7ee12efa151cb899c0d1f2d94ce0"},{"version":"26.5.0-alpha04","sha1":"7e06569f2635da6910a2a638c475bf48803f5f74"},{"version":"26.5.0-alpha05","sha1":"b09481aef9cbef037ab8f499879132ec903e1ae8"},{"version":"26.5.0-alpha06","sha1":"4de4499f1f50f09afc5eb26db68e74a8e6c93efd"},{"version":"26.5.0-alpha07","sha1":"5491b063b8d1e7042daf1bb2dbac6677d9ecd436"},{"version":"26.5.0-alpha08","sha1":"ae0f1b5168ee207d651da9cda4dbe6db9b7bd0af"},{"version":"26.5.0-alpha09","sha1":"2c5bf713dc5db42cbbdc8d601ef2ee6b7e69a291"},{"version":"26.5.0-alpha10","sha1":"fffa18987df7ac4723c2f905412ee98cf6f20eae"},{"version":"26.5.0-alpha11","sha1":"751b4f71c2531d7827881588018dbbbeafbe7829"},{"version":"26.5.0-alpha12","sha1":"93f306027e843268e991a62f6a0d767cc1d55246"},{"version":"26.5.0-alpha13","sha1":"9f109f905f8db668f834fe12dddd301775a2b066"},{"version":"26.5.0-beta01","sha1":"ed4a837fb61a1b95dd0ceb6a1d3556a302aad41"},{"version":"26.5.0-beta02","sha1":"65a3a8f80dc701baf698b16e0b20d7724080b82c"},{"version":"26.5.0-beta03","sha1":"2ca3aa4d36380a7ba1a96f34ae842c2fbb794bf9"},{"version":"26.5.0-beta04","sha1":"33ce8e5269ae01aea230b26a3855a2a6aacd5825"},{"version":"26.5.0-beta05","sha1":"3d15944a47593c58ab86b35531ee0d2565aff1b3"},{"version":"26.6.0-alpha01","sha1":"4f98145b248d297325774b7da357d684511fe22c"},{"version":"26.6.0-alpha02","sha1":"160be793046545eef784284c77f2ca530d1bd16"},{"version":"26.6.0-alpha03","sha1":"fecba2ff848ba85dc5176f9f267144c8721fa08a"},{"version":"26.6.0-alpha04","sha1":"fdd30b76529e7f0280cc8d33c1995bc896a3642b"}]}]},{"group":"com.android.tools.internal.build.test","update_time":-1,"packages":[{"package":"devicepool","versions":[{"version":"0.1","sha1":"b72d34675d62f548a23dc3582f5b51ac33873a26"}]}]},{"group":"com.android.tools.lint","update_time":-1,"packages":[{"package":"lint-tests","versions":[{"version":"26.0.0-alpha1","sha1":"b3add58015bd0cd6b51242233a7d5e9a36049cee"},{"version":"26.0.0-alpha2","sha1":"91ee795eadff778c2ae372a5a909e53f02888edb"},{"version":"26.0.0-alpha3","sha1":"bbef28ec0e41da1515e3ab0121866b94dfdf43ee"},{"version":"26.0.0-alpha4","sha1":"fd773989e393a6e05c01f5aabacb333474080766"},{"version":"26.0.0-alpha5","sha1":"6ef0e256fbb77451f25b9583d277393a15d5e247"},{"version":"26.0.0-alpha6","sha1":"d6dba0ca44c990e913ea4d0c1493a30ff5f7b03e"},{"version":"26.0.0-alpha7","sha1":"851098ad8b13facf8d0b11b3a194fd5b54f36932"},{"version":"26.0.0-alpha8","sha1":"343026c89c1c1a23b58d10d6e09070d262ca7cc6"},{"version":"26.0.0-alpha9","sha1":"7df816f51a1826c2508d6de7dc168d9c0aa47367"},{"version":"26.0.0-beta1","sha1":"6af6dbae54dd3f393af7c0607178e45b7aa0b7e6"},{"version":"26.0.0-beta2","sha1":"bf0d3aa6172e7c75b31c5c1b724860e1a6980640"},{"version":"26.0.0-beta3","sha1":"9c5435e02e432dd8f87c3c415b0ebf4aa4d4e4a"},{"version":"26.0.0-beta4","sha1":"16f7c8c75c94f633b39a0849e7939b62e4973fe4"},{"version":"26.0.0-beta5","sha1":"1a57076e925bdd6de94283d41c33d96d882be1f4"},{"version":"26.0.0-beta6","sha1":"d8c06393a69815992317dcf2a7d8e2ad46aa97c3"},{"version":"26.0.0-beta7","sha1":"817373700ea6ca8e8a1ec70aa4c9af71bc555bc"},{"version":"26.0.0-rc1","sha1":"a414479d31cec23223ebf82a2bf175df5f8890ac"},{"version":"26.0.0-rc2","sha1":"e919cdad1bf0bdf797889636a25a63415f947e0d"},{"version":"26.0.0","sha1":"7829e11d5cf45096c561739209c9e2a29c5982d5"},{"version":"26.0.1","sha1":"384ed7207ec02beae4f3217441986e65f1f2c412"},{"version":"26.1.0-alpha01","sha1":"a2902e1730374a2d6fa9cbcb2a3d3c2745673f01"},{"version":"26.1.0-alpha02","sha1":"38f95355d25856135f481f4f271be24ace5075b3"},{"version":"26.1.0-alpha03","sha1":"89bcba656fa6af40d0f9436ca5c3f3b53fc0099e"},{"version":"26.1.0-alpha04","sha1":"ed845aa8c6f61a33b70be4f7c6c9f579f0e0e41"},{"version":"26.1.0-alpha05","sha1":"4db72fb6e636f97ae572d272ef2d39235e37b874"},{"version":"26.1.0-alpha06","sha1":"f83ed197f111c6fffb75209261d74b588a28046c"},{"version":"26.1.0-alpha07","sha1":"ccabf97ace855f444dd13f2e80ea2be5a9a2b5d8"},{"version":"26.1.0-alpha08","sha1":"585b941606e83beb8505e66bab1f839ab3aa8dde"},{"version":"26.1.0-alpha09","sha1":"6944b170ccd4a5841209a7e041515dbc1927be6f"},{"version":"26.1.0-beta1","sha1":"700a6932f6ed89bd5e7dabbc7c61a0427af35a3"},{"version":"26.1.0-beta2","sha1":"c5a028122cc4f175e0a2118d11f4352ebb5d82ec"},{"version":"26.1.0-beta3","sha1":"2ddcae3520246f41b7924da032691e7cd91f0eab"},{"version":"26.1.0-beta4","sha1":"d66443415c903a3aaffa5a55f8463ec8820e25e6"},{"version":"26.1.0-rc01","sha1":"ce9ac57d8d40b8160ddeae16501f31f40de91533"},{"version":"26.1.0-rc02","sha1":"b5be60ec1ff74ce4a5b59fd6105a1b2d007c5f95"},{"version":"26.1.0-rc03","sha1":"937497a0c0f69da667754aa7551be2de08cdeddb"},{"version":"26.1.0","sha1":"b20c6543a08d7d1abc3f6ada6f30f83696d1e921"},{"version":"26.1.1","sha1":"3d54f27d88eebf92d8fa24010e55afafbbbed5ba"},{"version":"26.1.2","sha1":"9d4ffdf0048d9f7eb9775580db22a38cef24a76"},{"version":"26.1.3","sha1":"3fdacc8ebc2b8180fe1fa3bf16e7729ea3df6c61"},{"version":"26.1.4","sha1":"92384e876a9763bbcc090698fd763233ae5298d8"},{"version":"26.2.0-alpha01","sha1":"7ed4d877356429143c10b015efc647191542dcf6"},{"version":"26.2.0-alpha02","sha1":"59530df554bb1cc388983badafd73414a31daa36"},{"version":"26.2.0-alpha03","sha1":"f99b37fbe835996c73ecd08ed3fcccfa05e385d"},{"version":"26.2.0-alpha04","sha1":"dd1ba61b806af9ae30b63978d0aa4655b072d36f"},{"version":"26.2.0-alpha05","sha1":"899ed051476f4e6f73e7e970a4628cd98487b3b8"},{"version":"26.2.0-alpha06","sha1":"81569a3eca92c4a474c27ef0743eddfb77131f1c"},{"version":"26.2.0-alpha07","sha1":"eb6565e4d283fa157b2860bad3598add966de97c"},{"version":"26.2.0-alpha08","sha1":"a7fb11cafbc51242bfa26ab4b3d7af3694f38ad1"},{"version":"26.2.0-alpha09","sha1":"d78de459c5e2baece60bf446c28bb3665253557f"},{"version":"26.2.0-alpha10","sha1":"c8c673d5bc68ba266976b878cbb38ebcd2d08f0"},{"version":"26.2.0-alpha11","sha1":"4cb7297cdf909c25867b15ea3801349f47dc23f6"},{"version":"26.2.0-alpha12","sha1":"540032191a0901b03107623fa18341de02b7dd18"},{"version":"26.2.0-alpha13","sha1":"7e6d083f173cc7beca8dceff14896bdf28ac6340"},{"version":"26.2.0-alpha14","sha1":"ec06f2d3c02aabcc4831e5b53ae736ecfb85c7f1"},{"version":"26.2.0-alpha15","sha1":"a2dd1a4139e41b286d044b9312203fd7012a5b33"},{"version":"26.2.0-alpha16","sha1":"c664b091a2e2d214796730c965114e0783038a99"},{"version":"26.2.0-alpha17","sha1":"ad3e3c1f2393bf18dd9fff8d0fd5ed465f1f2f2"},{"version":"26.2.0-alpha18","sha1":"dc5c184e70af150944521a26b7b11fd168a456d0"},{"version":"26.2.0-beta01","sha1":"6763eaa34f6e3ff65ca7d157d2b4a946425ef2f4"},{"version":"26.2.0-beta02","sha1":"c302907c2ed7446866addee42ec1eceded38de4e"},{"version":"26.2.0-beta03","sha1":"bd48961f0027b5cc0630330bec77f631acad53cf"},{"version":"26.2.0-beta04","sha1":"f8ad73b77a72a33f33e3a5c4f430371237168fd9"},{"version":"26.2.0-beta05","sha1":"e2d8abb42bdcad229ab9f6fcbfaf53aa5fbbbe14"},{"version":"26.2.0-rc01","sha1":"580b6d320c152924fb6f8f5ff7b550afc8276991"},{"version":"26.2.0-rc02","sha1":"c9950843158a4949e9eca69c47ee7ed65cbf2c5e"},{"version":"26.2.0-rc03","sha1":"27673be801badd37d310dd6e27f140da2fde7a6d"},{"version":"26.2.0","sha1":"c67b20870763762378777748e82077cf2f175202"},{"version":"26.2.1","sha1":"df31b63b5e25b7e827f59c8464f488f75bee3b4c"},{"version":"26.3.0-alpha01","sha1":"af2659eea2d0c5b11383aab3baa57551b7f016a6"},{"version":"26.3.0-alpha02","sha1":"88ae4c59808d8e660bc7962e775566ec0c939568"},{"version":"26.3.0-alpha03","sha1":"4d9f248be37b18344e97ed16beabae5b08b027e0"},{"version":"26.3.0-alpha04","sha1":"7d0b9c56945a81bf05bf1cb42a6e2ea5f927e89c"},{"version":"26.3.0-alpha05","sha1":"5498bb4ef20741e42ec463fe7b768e71d2434d7a"},{"version":"26.3.0-alpha06","sha1":"946b4b9e860d2ac406f874555f64161b5e2530a8"},{"version":"26.3.0-alpha07","sha1":"19ba3073b141957ea51762f86f4ee6abb7ab7a8a"},{"version":"26.3.0-alpha08","sha1":"2246742f7a27e0981b1951029a1d460a24bdbfbb"},{"version":"26.3.0-alpha09","sha1":"75c490b5f59da152c2c9746577e94dc2d046ed89"},{"version":"26.3.0-alpha10","sha1":"1f516066913965d60abbe051862155a2835baeb9"},{"version":"26.3.0-alpha11","sha1":"f1254e193ffcfa6ea6ad3c22e940c07a34b57173"},{"version":"26.3.0-alpha12","sha1":"d00d6f740e6811cbf73f89ed4be82870f272a6a6"},{"version":"26.3.0-alpha13","sha1":"3c56d0987bd1f8a65a10d4814c63187c5a1e349"},{"version":"26.3.0-beta01","sha1":"d8fe794d99b0fb33738602b66c331327d9e1cf89"},{"version":"26.3.0-beta02","sha1":"6c4b97a881936fb072e4e06fad1ca1f249dfbc34"},{"version":"26.3.0-beta03","sha1":"a62150adfbcd09bac04ca14d23123bf23d1fd942"},{"version":"26.3.0-beta04","sha1":"a1f31ba575847ee43d7224828d4d846bc6b75d80"},{"version":"26.3.0-rc01","sha1":"29ed562a28bcd1c15a60a240d0252c6a2d2e99e5"},{"version":"26.3.0-rc02","sha1":"f0ff7fc38bcc34cd3f891674bad34e989ebd91fd"},{"version":"26.3.0-rc03","sha1":"f2da4cd80991decbcdddbb34777160b970d6a1e5"},{"version":"26.3.0","sha1":"76dea02369ed86732afc0fab5acdad441cfa51da"},{"version":"26.3.1","sha1":"64fbbb08773c3caf4a19e52ec7a643ba91fd7f4f"},{"version":"26.3.2","sha1":"ead12f6cee2e0a3eac6fb9fc76e23cc57b5b0514"},{"version":"26.4.0-alpha01","sha1":"f85c3d43d2085a49f6ab0a6a629953410d9a7ff6"},{"version":"26.4.0-alpha02","sha1":"8e0510b088b14b30c5b533d30dba9a6aaf4d5a9a"},{"version":"26.4.0-alpha03","sha1":"79e7b6c4339130d26b7672b0ec9948231495ccb"},{"version":"26.4.0-alpha04","sha1":"6f638392b93bc108b2ae9eca44d01c04fd014c0b"},{"version":"26.4.0-alpha05","sha1":"bec81240a8544daecf9a97ecb9a7cfe71b58acf7"},{"version":"26.4.0-alpha06","sha1":"b2ac1123cc73689191966985883dc4c45cdb15e0"},{"version":"26.4.0-alpha07","sha1":"23fe5a63c65df5519b96f3319dd27fae8ac9b615"},{"version":"26.4.0-alpha08","sha1":"15bd6b55e3f1db05c5ac961c23188614d33dee3f"},{"version":"26.4.0-alpha09","sha1":"eba7396a72d005b49eba88b0766d4089b6f3e15d"},{"version":"26.4.0-alpha10","sha1":"8cf80f2d58d4cb605b1a440d5d2098a592b138c2"},{"version":"26.4.0-beta01","sha1":"271807e654abd206b116a212bc41573ae94d7d1b"},{"version":"26.4.0-beta02","sha1":"b68dce5414564473c60ca63b285e06aff4906190"},{"version":"26.4.0-beta03","sha1":"2518ed638c1c13fe0d38c1ddbdc9de9204fff81c"},{"version":"26.4.0-beta04","sha1":"825f8d57e562d347eaa798746d1747ade38289e6"},{"version":"26.4.0-beta05","sha1":"827ffcf7fe5a5f30777ea4da8db0f9490b53088f"},{"version":"26.4.0-rc01","sha1":"ed4e7e77ea7bd0eaf01a22bb1ed44c2883888002"},{"version":"26.4.0-rc02","sha1":"a2ad10d66685d4313dabd1989197f410d28a6c04"},{"version":"26.4.0-rc03","sha1":"eb5da077fbc567352db9321c7e5420dfea23724f"},{"version":"26.4.0","sha1":"b9b0ae3289849b2539f92cfe50c9f3233ae65d45"},{"version":"26.4.1","sha1":"1ff15c5d66379cb23185ba8cda85588135cfc497"},{"version":"26.4.2","sha1":"a22a4eab0fa059f15738c488b595bf17b9c6730d"},{"version":"26.5.0-alpha01","sha1":"80e90624afe63645ded1a0f4a5bfccb998e2d926"},{"version":"26.5.0-alpha02","sha1":"143b7c8f101657a9f3d4cc4ffefd37f8b049f90d"},{"version":"26.5.0-alpha03","sha1":"6fcef875513152ef7a98eb9b262d3ceadbfaccf0"},{"version":"26.5.0-alpha04","sha1":"b714cdf2ced6bac59923ce4718d66e2bc9c02f93"},{"version":"26.5.0-alpha05","sha1":"56032bac2b578a4dc7808ec9aa6cf4ed1dd5d325"},{"version":"26.5.0-alpha06","sha1":"99b9819778d362aba162ec2fa5e8720a7d90754d"},{"version":"26.5.0-alpha07","sha1":"26d6c154fd2aa04150a8dd635b5d49ac999e546c"},{"version":"26.5.0-alpha08","sha1":"3edeac42df36853acfcff77d067dc9eb5d8c44c8"},{"version":"26.5.0-alpha09","sha1":"c9a2dc423bea4fb9605a02f1ef1af162c213bf37"},{"version":"26.5.0-alpha10","sha1":"8d868dacf7ada3d5c9de8f998af46c4c04c95884"},{"version":"26.5.0-alpha11","sha1":"30ef8fab7593786f717b2433229095e1ad7e653"},{"version":"26.5.0-alpha12","sha1":"ec07b76bb511889779d8da3c3d8679753c55f221"},{"version":"26.5.0-alpha13","sha1":"ba98f234a8ef4dde13a13fb5383760131cc55d94"},{"version":"26.5.0-beta01","sha1":"b28c8ae4004bec080e3e4fe94abd0c947d92476a"},{"version":"26.5.0-beta02","sha1":"3f646ab56b4332c0b9f8a12943f3888ffb564ff4"},{"version":"26.5.0-beta03","sha1":"5abe731b70982cbfcae8d9c9c9024dc0015356db"},{"version":"26.5.0-beta04","sha1":"cd152af864b25abb16953b6bca0fab4ae9dfc8aa"},{"version":"26.5.0-beta05","sha1":"deb6d566b098d3267380a5f9a8dbe13d7b1195f3"},{"version":"26.6.0-alpha01","sha1":"9593230d6569aa1877b718411fc561f55edaedbf"},{"version":"26.6.0-alpha02","sha1":"cdb52e5eaa0f4d5e66647b37ef4696cd6903845"},{"version":"26.6.0-alpha03","sha1":"b56194292710f0f252e726e35b70a831477db571"},{"version":"26.6.0-alpha04","sha1":"af86abee92f44927149fbf56d7e6ebdaf1876e0b"}]},{"package":"lint-api","versions":[{"version":"26.0.0-alpha1","sha1":"56aa597e82d7e7fae34ac7c67d8807f463749b91"},{"version":"26.0.0-alpha2","sha1":"100dd85048335f2f301415ccaeceaff399975b91"},{"version":"26.0.0-alpha3","sha1":"d9deacdd2b57ecc819a5541280e84c57849773d7"},{"version":"26.0.0-alpha4","sha1":"54a5b7cc04b59750cb139873d81fc4c2c5e30dde"},{"version":"26.0.0-alpha5","sha1":"99f775ae471b8beca085c397983f256ef50f93a1"},{"version":"26.0.0-alpha6","sha1":"73420d3302ca936f3947690ae32721ad82e0ffb0"},{"version":"26.0.0-alpha7","sha1":"184e40c1269ba18ffddc9fdf4fb64c2b2192b377"},{"version":"26.0.0-alpha8","sha1":"a4f345971c0a4bc057bdca1f58981a9e8c42a06d"},{"version":"26.0.0-alpha9","sha1":"ca89aee8c547d4743cf61db5a386a24a1e71a3da"},{"version":"26.0.0-beta1","sha1":"f8eaff6e13fcb1de3764a802f2bc8de80f6bde42"},{"version":"26.0.0-beta2","sha1":"e3c280b6147df6e3721ac57ca6ac71cc74d52751"},{"version":"26.0.0-beta3","sha1":"81ae6cfe9c91c6b8fd409dc8a504c82afc196a57"},{"version":"26.0.0-beta4","sha1":"63f0233a1454590613bdebf7a7d52dbc401b4893"},{"version":"26.0.0-beta5","sha1":"571f486710f64dd2120416982df324268fc89f32"},{"version":"26.0.0-beta6","sha1":"ceb75f183435f8bb818c9e56ad3ce818a2ecb028"},{"version":"26.0.0-beta7","sha1":"bb23babb720f3871581f06ef6f9da54da8619759"},{"version":"26.0.0-rc1","sha1":"4dc997d3cfa6b2226550d387ec98878d0739a911"},{"version":"26.0.0-rc2","sha1":"dbaa05cb3c68b5c082d7b85da041b0cd7020356d"},{"version":"26.0.0","sha1":"ba8981e318f342456c77a2420b9d9f0967eaf138"},{"version":"26.0.1","sha1":"2d4dd9f4676fbb152e4baf6f6f4cbbb868521832"},{"version":"26.1.0-alpha01","sha1":"2c95bbdfef590cdd979a1aa570cc20a7a7df3673"},{"version":"26.1.0-alpha02","sha1":"76032a0139ae6aa1e7c070fe2d64d863e54c746a"},{"version":"26.1.0-alpha03","sha1":"ba1424707d8c9dfec518539145b83b9e9df868d3"},{"version":"26.1.0-alpha04","sha1":"632b5ae16e4242ce5aeebddc9d5b5b3302965ea8"},{"version":"26.1.0-alpha05","sha1":"f291ae575219b8ff808def4c56fc0349d9bbc46b"},{"version":"26.1.0-alpha06","sha1":"497ba68da854b759b8137824f77e18818a1d9197"},{"version":"26.1.0-alpha07","sha1":"917ff97e611ada38b7a3b3c2542f8f90b745f61"},{"version":"26.1.0-alpha08","sha1":"c72fe73df07cc4af379b879203117ad051458b6d"},{"version":"26.1.0-alpha09","sha1":"3fc6de34f2eb05f7ba6086b5bb01e393a658d636"},{"version":"26.1.0-beta1","sha1":"22868e78f7589ffec86a6d33445b7965f1557d63"},{"version":"26.1.0-beta2","sha1":"aeb76c6921e65afc7f17b43125b6ccd9a6bd177c"},{"version":"26.1.0-beta3","sha1":"c169841a67c58cf11e2b51365cf0a3f3ce54db04"},{"version":"26.1.0-beta4","sha1":"41862ba0009c85731b1353e63e6e949e66f20f86"},{"version":"26.1.0-rc01","sha1":"d71623f57eef4a7d81337e10053b57f5c5895479"},{"version":"26.1.0-rc02","sha1":"847461ae1c7ffe7b0bcff3de6ccec9e5fb3b79f6"},{"version":"26.1.0-rc03","sha1":"2fdac1df3311bc7c74ee971daddb974d8acbd17"},{"version":"26.1.0","sha1":"5037dfbde85965e0f08d1d225bfc8c9fecc9d104"},{"version":"26.1.1","sha1":"511cfe77d5058a6cf7a4b40af66f8b60fe192d59"},{"version":"26.1.2","sha1":"cc549ca47cfbd4956992f54255565f6a38f9c38f"},{"version":"26.1.3","sha1":"61e27d579b88ca4fedd0fa5a8006c80c7556b4b"},{"version":"26.1.4","sha1":"6a9b3e9f0f1b26c6b41a25f34292fab8cf80db0c"},{"version":"26.2.0-alpha01","sha1":"beed44470aa84ef19beaae4b66e097e7ce94ce10"},{"version":"26.2.0-alpha02","sha1":"d2a0aa85ad0e7771cc01f0f4a810498d7f4cdd57"},{"version":"26.2.0-alpha03","sha1":"69d446bb9fdd4127fe226f86d6ab985647ca9958"},{"version":"26.2.0-alpha04","sha1":"f146210fa7d87aab2997332f0dbd521298966d8c"},{"version":"26.2.0-alpha05","sha1":"d0ddc506c816a3f1d9bac0e7ba1cb8ce6f737c9e"},{"version":"26.2.0-alpha06","sha1":"7962bf5e12eb40ff8e28b27b41de17fa8cadf394"},{"version":"26.2.0-alpha07","sha1":"db84e8f256bb77206318f1958048f8d28421d658"},{"version":"26.2.0-alpha08","sha1":"d41ea0f28f17b9b1c775f8526c7fc1e93d32968"},{"version":"26.2.0-alpha09","sha1":"6422517796d795966fd032d314c922ba1fbfafcb"},{"version":"26.2.0-alpha10","sha1":"75b531b19c58be702de6491ebb42d4cd992bf168"},{"version":"26.2.0-alpha11","sha1":"6aab49824a1351f2d46f1a950f3d18d25f22cf48"},{"version":"26.2.0-alpha12","sha1":"6eace5380382707e78700bc7afd053bfabba80b2"},{"version":"26.2.0-alpha13","sha1":"2bda2912c3c25fb5e405b42e31012b22bb211c0d"},{"version":"26.2.0-alpha14","sha1":"ba96f6339e66f2c0f6101e7cc4a4d2d4cf843a48"},{"version":"26.2.0-alpha15","sha1":"3d296a22788621b39994b97cff1f6fbd82a074fc"},{"version":"26.2.0-alpha16","sha1":"50486684aeb7f8891adf0a4fc68cc94378d4c1f5"},{"version":"26.2.0-alpha17","sha1":"89719292bb624d4f2a80f04dd1ef70d87d39659e"},{"version":"26.2.0-alpha18","sha1":"22aa41a93f1e57860b93ffadb2e1b16e027092d8"},{"version":"26.2.0-beta01","sha1":"a3e554b8806f53c232e5479f7233a46f74776334"},{"version":"26.2.0-beta02","sha1":"42fd26a3aed981003a135ff2459e079f09a85f7"},{"version":"26.2.0-beta03","sha1":"9636db0e3656a16f724a5281e0d24104b209ae44"},{"version":"26.2.0-beta04","sha1":"817a4f82d5819f03960daa9cfa1838f6b9e7df5a"},{"version":"26.2.0-beta05","sha1":"230d8fdee2a25874806629f8d61a6698ab0b3adc"},{"version":"26.2.0-rc01","sha1":"4b7efae191a44bb485b7bdd7fb48898a7846219f"},{"version":"26.2.0-rc02","sha1":"dafd591cb0400deff6fdb70def927dcdf896ed4"},{"version":"26.2.0-rc03","sha1":"e1d3359b6c9b91a77ce62d2ac817fb08dae88a14"},{"version":"26.2.0","sha1":"9977968f79f55c0b18434dd15e401f380a300510"},{"version":"26.2.1","sha1":"96dc0b53364f07670876d730fa1e7262327d52a4"},{"version":"26.3.0-alpha01","sha1":"fd74c95b2642b8ae2eaf9ed667d041226d797c1a"},{"version":"26.3.0-alpha02","sha1":"430c0f4e81f93dcd9eb12e6e75b30c562fb6b0ae"},{"version":"26.3.0-alpha03","sha1":"7d5e12e6ebe98bd3b568bacd94a1fda8961ab436"},{"version":"26.3.0-alpha04","sha1":"e70712fc9f8a17dee3e603705de681a00010f3a"},{"version":"26.3.0-alpha05","sha1":"714b58f82c8c153a463794870a1d76349d300350"},{"version":"26.3.0-alpha06","sha1":"e8f467aaca85f5d56070c365ef6d8b93f7ab2047"},{"version":"26.3.0-alpha07","sha1":"8724af740fe65d5bf1526015b6e85a8fe1a8ac0a"},{"version":"26.3.0-alpha08","sha1":"6249b5d5be6f33c819aa9f9725d73579bde1d151"},{"version":"26.3.0-alpha09","sha1":"d36f223af345f52b143ff02f8be4ab336e15fab8"},{"version":"26.3.0-alpha10","sha1":"1038c7ca3ec191a7e2498427f585531a096b17c3"},{"version":"26.3.0-alpha11","sha1":"93e110ee4819ebe4aedfd00e8e5062b13399a8eb"},{"version":"26.3.0-alpha12","sha1":"c6161529a53c016128c4b54a3849d6272472460c"},{"version":"26.3.0-alpha13","sha1":"67d1fb286022d59fc1ac538f5b95d80516c66c6d"},{"version":"26.3.0-beta01","sha1":"b4ae5c29834574703970b7d5161bb1678edc8816"},{"version":"26.3.0-beta02","sha1":"18df9c30a1ce676a0bfea1cd70e0ebf93eb2b6ed"},{"version":"26.3.0-beta03","sha1":"4922a19e8dc6a341084efb1dda4993faed1af94b"},{"version":"26.3.0-beta04","sha1":"26ecbf62e0211d28d7673737d66312b6234f2704"},{"version":"26.3.0-rc01","sha1":"d6cc4f85b96392cc43942b7863dc52d5ea32dc04"},{"version":"26.3.0-rc02","sha1":"e203f703f2f7d3de2038409769541e55dadd279f"},{"version":"26.3.0-rc03","sha1":"69ed21bd607f1d6997b8f50e10fb16589fba2f9b"},{"version":"26.3.0","sha1":"4002fa78b2f343309db2c0dd6858f1da4ba8fe2c"},{"version":"26.3.1","sha1":"b630e36ffd4511d9e01d606aa3868cfce6f18b9f"},{"version":"26.3.2","sha1":"992995e7e4c1d183cc0b0e71382b53235c54084c"},{"version":"26.4.0-alpha01","sha1":"4f78f5421a406401d8368896e1aa717c3ed3f9b0"},{"version":"26.4.0-alpha02","sha1":"a61be4316046a3a9135a4754a20e93b8483bb4fb"},{"version":"26.4.0-alpha03","sha1":"8d983e9dbf386ca938ed60a552bafa3f5e274592"},{"version":"26.4.0-alpha04","sha1":"3cf8ad67b0a82bbc6357d3212500b11d21b1beea"},{"version":"26.4.0-alpha05","sha1":"2c3b120810fac5a5f092fbee890097c329968085"},{"version":"26.4.0-alpha06","sha1":"a0d4d3f75ed79ed78f314fbd3b153e032b1f97d9"},{"version":"26.4.0-alpha07","sha1":"272b221b38c44ede5363e60a92341bf158c26786"},{"version":"26.4.0-alpha08","sha1":"6cfaf2be01ff39195b826b5d4b9b4db3e2995c89"},{"version":"26.4.0-alpha09","sha1":"58441382cc35f6f6c1642103bfd6eef8dddea0c4"},{"version":"26.4.0-alpha10","sha1":"6aea1e5e741ec2121108069a2adadcf883556451"},{"version":"26.4.0-beta01","sha1":"9387a9c7fbee5520a005a4c8e9e7fc5a7b804072"},{"version":"26.4.0-beta02","sha1":"49b75f62a901d1701ae06525e82ab417ac8408fd"},{"version":"26.4.0-beta03","sha1":"94b753d4c09cd90c7ea3f31a677d1d417f7f0506"},{"version":"26.4.0-beta04","sha1":"c1889ddbf3f31357ecda452c2d7d21c95dd1783b"},{"version":"26.4.0-beta05","sha1":"3cbf7388074e895c6ec7fc4535f67adef8c59707"},{"version":"26.4.0-rc01","sha1":"53b61d9ae75327a070312af98daab927fa86354b"},{"version":"26.4.0-rc02","sha1":"424a7d503596055550e77a04df7213e3452927e0"},{"version":"26.4.0-rc03","sha1":"4e5a6f71df0967342f26b7e2278d75e87b66d983"},{"version":"26.4.0","sha1":"64e9a28302b33cac21d37ca0cdcaea88941e357c"},{"version":"26.4.1","sha1":"31140461945f4be15e5fb0dbf599b4a5bf0ceddb"},{"version":"26.4.2","sha1":"f7138e14eb007b469f368f69b06aed1d51b8afcc"},{"version":"26.5.0-alpha01","sha1":"e4fe7e28d9056fdc70aacca3eccb2a239a060181"},{"version":"26.5.0-alpha02","sha1":"a843be62d1219d6390354a297139d8c932316d70"},{"version":"26.5.0-alpha03","sha1":"34b7009c70d232084a382f59aadd1ee9b9ac36bc"},{"version":"26.5.0-alpha04","sha1":"e44ca854bb0f6ec3b7eaf9657844940f2f1125b2"},{"version":"26.5.0-alpha05","sha1":"7cc4cc672178211fe89210dd1185916a0f035aa2"},{"version":"26.5.0-alpha06","sha1":"d323646a1f13991208beefe2d7d26019e90ded4f"},{"version":"26.5.0-alpha07","sha1":"7feaf83880d7d57eff55393aafe3a38f1afe0136"},{"version":"26.5.0-alpha08","sha1":"c51f74e12ae0cc921954092df1b07afc385b4b53"},{"version":"26.5.0-alpha09","sha1":"95b906ed66bce029547749ffd37da04c53884cc9"},{"version":"26.5.0-alpha10","sha1":"46441153676ac132c67503b19fc55995eb2ca65e"},{"version":"26.5.0-alpha11","sha1":"b3ed55f115d6bc8ad29f076c46b1ca590f18badd"},{"version":"26.5.0-alpha12","sha1":"e4375420047ea123601a19d5d6dc77c8a190536"},{"version":"26.5.0-alpha13","sha1":"11f4cc0481d6063da47f65f3ebec2f6134deb6bc"},{"version":"26.5.0-beta01","sha1":"20fe5da19ebe156d9e3b71ebc4ccfa78d2bfdc11"},{"version":"26.5.0-beta02","sha1":"77f542f920acd587d4fa0354edc3aa06eb3d31f1"},{"version":"26.5.0-beta03","sha1":"c6b6b42020f8162f6320db1e94d0283cc20c4870"},{"version":"26.5.0-beta04","sha1":"49bdf104f059e6f3bf895d4245468f45917edfeb"},{"version":"26.5.0-beta05","sha1":"14ff62bd250a693a75c7a825556f26799794194f"},{"version":"26.6.0-alpha01","sha1":"342f5d19e19883c5a93a2b0a1d1d95c436074a5e"},{"version":"26.6.0-alpha02","sha1":"6023669feecd5b50aa02112913b9704ef14eecf1"},{"version":"26.6.0-alpha03","sha1":"665723ed3456f34b74bd338cf6580dff37905db1"},{"version":"26.6.0-alpha04","sha1":"f178dfee0898fec4b89bdb2626e9b1a537a1c2c7"}]},{"package":"lint-checks","versions":[{"version":"26.0.0-alpha1","sha1":"3f1a40c6cb4e09ff38fc1076cdf66dedcd9f04b"},{"version":"26.0.0-alpha2","sha1":"27c19740e87498a9cddbade6201f3a918a4fc9a6"},{"version":"26.0.0-alpha3","sha1":"fc6591e41bfb6860c46d2b001e23837b0ca2e1c4"},{"version":"26.0.0-alpha4","sha1":"8f8d9a85f41f815745b5009bccf49fed0fb58201"},{"version":"26.0.0-alpha5","sha1":"276d3376f42fe6d891ac98ae3e8be87cb57256c"},{"version":"26.0.0-alpha6","sha1":"9e8c04bfb35a2f43d60abf09e8f6b07bbfd99ebc"},{"version":"26.0.0-alpha7","sha1":"88684c075d5750d61d9f6cf6ce60c33d8da00cb6"},{"version":"26.0.0-alpha8","sha1":"77fc6f95aeb8a4a1f8822beb121d323e6ecb79a8"},{"version":"26.0.0-alpha9","sha1":"fa7e672e167a8120f2605a555339ce5b4655da78"},{"version":"26.0.0-beta1","sha1":"b46addd82128fb18c77260bc1a81106a6eb6f3bc"},{"version":"26.0.0-beta2","sha1":"c908e13cfd3cbab651e64adfe72690a1fe9a65b4"},{"version":"26.0.0-beta3","sha1":"7825b9b806189701a4b284bf5d59c2744287d417"},{"version":"26.0.0-beta4","sha1":"2415144760fc6fe4904c9a3fec23bd7ea52bd308"},{"version":"26.0.0-beta5","sha1":"fd49ad3b20aca48b291123162fd749c9112ea006"},{"version":"26.0.0-beta6","sha1":"19595bd82fc750e265c8d8756c2d282e67a15fa9"},{"version":"26.0.0-beta7","sha1":"3374a0aceda9bb18b4d02b6f597326943464c98d"},{"version":"26.0.0-rc1","sha1":"ee797bd792e4d355c11910ea75d665b6b904b6b6"},{"version":"26.0.0-rc2","sha1":"2c64e850bf9e98cd768a5f0871000d6cd0cf53b6"},{"version":"26.0.0","sha1":"fe04d508baa247a0d32cafbfd158ca439f875dbd"},{"version":"26.0.1","sha1":"a5df32c64598e190808accaa03820fb9db9f8201"},{"version":"26.1.0-alpha01","sha1":"b92403a44860ca72524d60d1165a703e626345b4"},{"version":"26.1.0-alpha02","sha1":"8b8d282decf7ff43bf49a1ea1e604c1cb336d052"},{"version":"26.1.0-alpha03","sha1":"96ba22509f4935339d70efe74f2791148e1dd7b4"},{"version":"26.1.0-alpha04","sha1":"6ef21eb917b18f76e14cf9bab0dbde2c64d648c2"},{"version":"26.1.0-alpha05","sha1":"d55dcb52ebf132bafd7442d7bea558f26ef6a57f"},{"version":"26.1.0-alpha06","sha1":"1c7627b171e47046222d39af31c46dd87b2507f5"},{"version":"26.1.0-alpha07","sha1":"ce14371fda823cdaf20bf167f0e45d9f4f57d184"},{"version":"26.1.0-alpha08","sha1":"1b9fd3095d6997959a1edd782f5ab31997b88942"},{"version":"26.1.0-alpha09","sha1":"959c248b7e46bf782c53160e60f4b955d6d714fe"},{"version":"26.1.0-beta1","sha1":"88210e472fd5827eb8a4fdbd8d5f0e3bbf6b1b6b"},{"version":"26.1.0-beta2","sha1":"5f29b9aace861ed18b7d53018566e97829ca84d9"},{"version":"26.1.0-beta3","sha1":"3482f1e020652c391efd74d2f9087c7cbcd00279"},{"version":"26.1.0-beta4","sha1":"a1f99bb6e533e769c57b555f3f444d0416c9d64c"},{"version":"26.1.0-rc01","sha1":"56ec8567616d624983cb2785f84ed3456acd95c3"},{"version":"26.1.0-rc02","sha1":"15890f6c0292e49fed7b7ea42b99adfae00131de"},{"version":"26.1.0-rc03","sha1":"d17975ef344a2718aae49a42eb796eed4f6be2e0"},{"version":"26.1.0","sha1":"b2621cdb74f817406e4448e215456e9df1718050"},{"version":"26.1.1","sha1":"8e8f4ca075cf9c845cba119d3f7db720773ab417"},{"version":"26.1.2","sha1":"757c9071ab13d3e4281d97e4698bb4c22285a8cc"},{"version":"26.1.3","sha1":"ca584ee7eb30814288ae3d394293a8f58ba95400"},{"version":"26.1.4","sha1":"c523768b07d24763171ba85b61ea296543a8700d"},{"version":"26.2.0-alpha01","sha1":"805e04ef7a3a8411fd8f5436ffdd93aee8e8d937"},{"version":"26.2.0-alpha02","sha1":"db7b7a51187ac62ea00b3c5408ef34cc02e2c662"},{"version":"26.2.0-alpha03","sha1":"980450b16727da2592e1f64f24a9a4e9cce00593"},{"version":"26.2.0-alpha04","sha1":"30bfae6bfd00ab5de235f24c16f3eaf08ea8d8d8"},{"version":"26.2.0-alpha05","sha1":"be81f657c1c36a7d3ffee7a584a60e405c76d4f6"},{"version":"26.2.0-alpha06","sha1":"a8e0d310ecdebcba3bb02a8326edf6a8d0d9991b"},{"version":"26.2.0-alpha07","sha1":"d2b47052b1ce2513721a8592d05e0b312c918ac0"},{"version":"26.2.0-alpha08","sha1":"9c6d2d985faaf2cc44c1173418ba04a83f286329"},{"version":"26.2.0-alpha09","sha1":"485bfd116d68af98de7100a5754868f17888f1f9"},{"version":"26.2.0-alpha10","sha1":"6b976b13f78ffe96734790219d547368a3863b01"},{"version":"26.2.0-alpha11","sha1":"fbcfd23930984560b2932dd31e28e77bb87d6a31"},{"version":"26.2.0-alpha12","sha1":"97d4c10767d8a41727cba3e40453a6441e001ff7"},{"version":"26.2.0-alpha13","sha1":"56007e4542f37c5b9e18616b250b417177a917a1"},{"version":"26.2.0-alpha14","sha1":"baf5f1f43733344ebeba3668f71459ffa0f332cc"},{"version":"26.2.0-alpha15","sha1":"12428e8aa4bc96f8d4d845b8547329c9f8a879c5"},{"version":"26.2.0-alpha16","sha1":"ac54aed77f1d8765f04f8fbd19acbae6ad68fc46"},{"version":"26.2.0-alpha17","sha1":"64458c4d1d6e443f873e833a481a28e4476dc134"},{"version":"26.2.0-alpha18","sha1":"30817a685d9581c2bb96d11d571068cd3c462ed9"},{"version":"26.2.0-beta01","sha1":"ff67cfe78f80f6a9ebff3ccf8e16c109f9633908"},{"version":"26.2.0-beta02","sha1":"9dd33cf86f8b64ef66a3ddb416cf955fd2b3d536"},{"version":"26.2.0-beta03","sha1":"296af5c24d9ed50e057a3ad93856786ada4df8e8"},{"version":"26.2.0-beta04","sha1":"feb3dda4e19e13098cf06a8abc983c16184f4f3e"},{"version":"26.2.0-beta05","sha1":"74d43930ae45d4ccd7a056ed541ea4b330edff0c"},{"version":"26.2.0-rc01","sha1":"7f6d78a3ff7c4f64766a16921c959b07c1fb88b5"},{"version":"26.2.0-rc02","sha1":"f1b3e34b7a3428b3a5d8a614c9729389476d0946"},{"version":"26.2.0-rc03","sha1":"b35e9bb4a40d5f33349dec7f501222b8b6bec797"},{"version":"26.2.0","sha1":"27d845c978e30e53de4d312678e74d4787989c43"},{"version":"26.2.1","sha1":"fdbb11b3f41db5bec11800b7431f1539c68f9563"},{"version":"26.3.0-alpha01","sha1":"b90e893556cbc6ce07305ceeba9b69416bd43fe7"},{"version":"26.3.0-alpha02","sha1":"cd1a3db53b73f8d48f911d417331b1aa1f6f2d09"},{"version":"26.3.0-alpha03","sha1":"bedc57bf566f103e4085d34d291d365224d38677"},{"version":"26.3.0-alpha04","sha1":"b4cdbcefe61e4c199b85d1855603abf292a13125"},{"version":"26.3.0-alpha05","sha1":"60bf9d6fc5f90c0494e1dd946f7b2d272b631cbf"},{"version":"26.3.0-alpha06","sha1":"23a9ee5951d4f4b59db9532e2190195b27d1a697"},{"version":"26.3.0-alpha07","sha1":"ff45c6f70a024aa10579bdfaa05bb41883c2fbe6"},{"version":"26.3.0-alpha08","sha1":"2b886b62a3049de64ee6c0cbe11a7bc16e732df1"},{"version":"26.3.0-alpha09","sha1":"188b030d429d61745d83220b7cadb9fa5175d9e1"},{"version":"26.3.0-alpha10","sha1":"7a39a91b02b1758b0ce1322245f50afac42ccf55"},{"version":"26.3.0-alpha11","sha1":"807597a94cf797785e5638fde421c4bf70a38687"},{"version":"26.3.0-alpha12","sha1":"dd657067c8cba2428f305aae86ef94e054493ac9"},{"version":"26.3.0-alpha13","sha1":"bc5b4caf74a8371da483cbd4e03cccccd83720d5"},{"version":"26.3.0-beta01","sha1":"d2204528f7bf596a91c97f6443fb9e8aa08235b2"},{"version":"26.3.0-beta02","sha1":"45e8fe68700d83d81921702254955ff9b0d1b9cf"},{"version":"26.3.0-beta03","sha1":"a99cf1394bab88af70b7091a94756c50c336791"},{"version":"26.3.0-beta04","sha1":"19a1aa49c69de6d1023c711ee3656d351449c155"},{"version":"26.3.0-rc01","sha1":"411f46f27abd85b7f82b0a80cb3514c228509754"},{"version":"26.3.0-rc02","sha1":"6f641cae25a5974124ea5e38e963c7885e2e023e"},{"version":"26.3.0-rc03","sha1":"a630175adf033b2442a4520534e19a639d598111"},{"version":"26.3.0","sha1":"f2bf46d3912e21c5575cf006ae201ba79f3d957e"},{"version":"26.3.1","sha1":"188bfd4ed5bfe45af94e2bd3359c6c9aed121841"},{"version":"26.3.2","sha1":"5b545a7f2ca1325bf470847eca6118f6f143497"},{"version":"26.4.0-alpha01","sha1":"b449602f7719c3bbcdbc6dd84d78f2642940f7bd"},{"version":"26.4.0-alpha02","sha1":"da93d90edccad9b4ffbbee251887eeeea47c857f"},{"version":"26.4.0-alpha03","sha1":"1aedd290129ff3266c52ff357fb03287f0c42f0d"},{"version":"26.4.0-alpha04","sha1":"57434c4d0872375e4ffe926da7e7c1ceea5191f0"},{"version":"26.4.0-alpha05","sha1":"74b2ed26da86ab39c123c82e1589107765b94ce5"},{"version":"26.4.0-alpha06","sha1":"98436d16ff93bb664fa82d72a5c147725b344a25"},{"version":"26.4.0-alpha07","sha1":"5b3c9af2be8fe69d079cef32eecbd32673b3e7dc"},{"version":"26.4.0-alpha08","sha1":"56f752cb6fc19980d62f2f47e3c849c86d6b5937"},{"version":"26.4.0-alpha09","sha1":"c9d9b36e26a3db6e88983f1d84331a7adf87732c"},{"version":"26.4.0-alpha10","sha1":"7a751fd699c0be86ae1f0034a7e1d354d9ab9b13"},{"version":"26.4.0-beta01","sha1":"b559b614a895b6c5eb70a30dd2e61956f3b94717"},{"version":"26.4.0-beta02","sha1":"ceac1d1cee497bce592b89487b7462b8ec1a97e0"},{"version":"26.4.0-beta03","sha1":"4e86983b07db13a6bc8b446f47cc37c310904349"},{"version":"26.4.0-beta04","sha1":"c12a870b9843399a2286f77a65e38ecebdf64001"},{"version":"26.4.0-beta05","sha1":"504773fb846eb1e75ffa45b57396f5eace69496a"},{"version":"26.4.0-rc01","sha1":"6715eb798a6ec5b001b546b5c002ec56903cbb19"},{"version":"26.4.0-rc02","sha1":"90ab2206444b008f37b3ea751c2bf2b4af309a15"},{"version":"26.4.0-rc03","sha1":"fcd9df8116833ca62fa81691b81c27f54fb99ad7"},{"version":"26.4.0","sha1":"f895005c04f026e3db496cf0f9cf3582b720995a"},{"version":"26.4.1","sha1":"70dfd9a22e5b481571f41536f7791515b6b9404c"},{"version":"26.4.2","sha1":"5c3929f03eae44fa8edf025597630881e5496abb"},{"version":"26.5.0-alpha01","sha1":"8de94d0259de7a31eddbba5f1c588d376ee0b357"},{"version":"26.5.0-alpha02","sha1":"5c4794e8c794b6f65ea259ec3cab78b027bd225c"},{"version":"26.5.0-alpha03","sha1":"8ea1cfd4dabbe7c782b370c8cdc256b406ba9e06"},{"version":"26.5.0-alpha04","sha1":"59abc549509df361e2a6df3551a34c58bf28e8ae"},{"version":"26.5.0-alpha05","sha1":"52ab3c951aed8f1b46368bb4b083120a11f4907e"},{"version":"26.5.0-alpha06","sha1":"4723f69fd6af2a41c8982b8ea684cd49559d0eab"},{"version":"26.5.0-alpha07","sha1":"1cdaf9cbb790d8439bfe0be0aafc6dd97ba73d93"},{"version":"26.5.0-alpha08","sha1":"a4d20e916af17d0951e7286ad56a021ff8f15392"},{"version":"26.5.0-alpha09","sha1":"1a3d9b50f386250381ce21b1ef67323fbdd0779b"},{"version":"26.5.0-alpha10","sha1":"4ab33218de5c2f87d0445ac4b6a7e25ed75c5118"},{"version":"26.5.0-alpha11","sha1":"2003a349653fa0f51cfadd9236bbcbaf24e9cebc"},{"version":"26.5.0-alpha12","sha1":"193882a5493282d72844165feb1aa73b2becbbee"},{"version":"26.5.0-alpha13","sha1":"864bba143e8cbb3077d52f109dab3b4a97d766d"},{"version":"26.5.0-beta01","sha1":"4c2da3556966782bb1e90f41f5f48eb09fadd0b3"},{"version":"26.5.0-beta02","sha1":"50a8f078426bd4edb68a07a5ee2f6c460ee76434"},{"version":"26.5.0-beta03","sha1":"2fa999be52ec96d0d6b73ef4a1f3e636d08b7182"},{"version":"26.5.0-beta04","sha1":"a4f77d6f4f59d7499682d531e6a9a35e096b36f5"},{"version":"26.5.0-beta05","sha1":"40d7615be4e8bed0340d7eb6cb72c028da1b0c93"},{"version":"26.6.0-alpha01","sha1":"fdc4287e3652922643aefb781a948cd713912761"},{"version":"26.6.0-alpha02","sha1":"ca75e5f496781fae1a9948ab9bcd9c60195f1452"},{"version":"26.6.0-alpha03","sha1":"106221efd68a8e84fd9c496040846b21b3c17707"},{"version":"26.6.0-alpha04","sha1":"bc4a74da6b59ee97d2bd7a4f348609aa96a5d1f"}]},{"package":"lint","versions":[{"version":"26.0.0-alpha1","sha1":"b6451e4ff0048ac6e843e6413d40f84d8f19d53a"},{"version":"26.0.0-alpha2","sha1":"f8ab18cc080202525734d6dfff06ff3a518eec86"},{"version":"26.0.0-alpha3","sha1":"40921a9f8f77d5512858c7e4d5586b3e7dd1cf1d"},{"version":"26.0.0-alpha4","sha1":"584f53e5d59bd237402447dfc2b303c3d785ab93"},{"version":"26.0.0-alpha5","sha1":"97017f9b8e0d071ba86c85460ac17c12ecfc103f"},{"version":"26.0.0-alpha6","sha1":"e77940b9be403fceee8207fc61f432125d7a907b"},{"version":"26.0.0-alpha7","sha1":"b9a1904875e3cb3b2d5a704226da87260afc7872"},{"version":"26.0.0-alpha8","sha1":"bd6cedfa13644367f9b1ce8a3c08b17a3fea9a15"},{"version":"26.0.0-alpha9","sha1":"61ca55fd17b28f2c3f784f1521758c424b0b199e"},{"version":"26.0.0-beta1","sha1":"9cec6855071f7a619e01c375b8155b22f2da7429"},{"version":"26.0.0-beta2","sha1":"5ed3f7fe6f5c9e8faea2ca26d0510efc0bd092eb"},{"version":"26.0.0-beta3","sha1":"e237968f5cb5eaa44552a3aad2205dcf9366542c"},{"version":"26.0.0-beta4","sha1":"f5a8816f964899d842ebfe4aa7f33554c64bf732"},{"version":"26.0.0-beta5","sha1":"ceff2d3ab36020d0f3a2c87f028b6f28c9c0a4ca"},{"version":"26.0.0-beta6","sha1":"8cc61858ba087565472353bb4672ceb6a53bc8fe"},{"version":"26.0.0-beta7","sha1":"ad5b1022daf4616c5e0553196620e0086030753a"},{"version":"26.0.0-rc1","sha1":"ea2c852459aaab68d717426837eaf9598b56d1c8"},{"version":"26.0.0-rc2","sha1":"63bc62de5eb5b9a43e3f71916c4374ab9c32729"},{"version":"26.0.0","sha1":"6c58c8b0066addaaffd174b78d701948b819cf23"},{"version":"26.0.1","sha1":"541bfb082a95a3b55d0158d69b067f1d7c623122"},{"version":"26.1.0-alpha01","sha1":"fe958cb8dc05bae9b44275ab29ea43e9caf84b6"},{"version":"26.1.0-alpha02","sha1":"b7d7ab1521bbe418cea7649a329339079e9c82c1"},{"version":"26.1.0-alpha03","sha1":"ebb32fadebe9e43aba6ef8137d7a341c5c7e6983"},{"version":"26.1.0-alpha04","sha1":"b3f5869afd634bde7d04d27627ce11c9be1b3d72"},{"version":"26.1.0-alpha05","sha1":"43d2526846d926ea0f6510f5e4a7d29300ff06c4"},{"version":"26.1.0-alpha06","sha1":"d2ae0c84e4977eb86137967d62db8ade1e58975d"},{"version":"26.1.0-alpha07","sha1":"a66d87e68f0d3eedcae1c506bb9f4cfcc7dd3f60"},{"version":"26.1.0-alpha08","sha1":"41f605879276a93c5a76bb4d9d62b62545c87c0"},{"version":"26.1.0-alpha09","sha1":"66e385b5c38d23a1a7fe25f1200925cf9b60302"},{"version":"26.1.0-beta1","sha1":"4df888c09221915f091233e93b7ca810807cba9c"},{"version":"26.1.0-beta2","sha1":"2be4ade57c7d33e52bc87c9716505e4f3ab94a1c"},{"version":"26.1.0-beta3","sha1":"39597f4a926673c61a043da00f0f50a3d12c5f81"},{"version":"26.1.0-beta4","sha1":"9bb15388a2cbe31664dbc7c6701a0fdb4b8c9f58"},{"version":"26.1.0-rc01","sha1":"b6b5f8c92d85323ff0b780aa3b8a7f9af3c426e0"},{"version":"26.1.0-rc02","sha1":"ba95d3cd17ec40f803a0e34feb6c8d8d5acacf9a"},{"version":"26.1.0-rc03","sha1":"1b404751560461a6c283b3688629bde1afaaf7b0"},{"version":"26.1.0","sha1":"5f7a2b81ad59ea8fc7052d285aba3fa68d128d3"},{"version":"26.1.1","sha1":"143f50cb11211b687aba52452951514bb16e79ef"},{"version":"26.1.2","sha1":"cdac9912c50ccedfda4f4d1e3eb52505b9be5803"},{"version":"26.1.3","sha1":"6eca2e21bbd1cbc57a5d1946ce20478de9849f5a"},{"version":"26.1.4","sha1":"38c2b30ebe6e4189b29eda8fc0724eb6272a59bc"},{"version":"26.2.0-alpha01","sha1":"6df1bb1862c0ff0e8dd921568026ed6210f238fc"},{"version":"26.2.0-alpha02","sha1":"e138d161b8aecdcaa402423f7b34be73d3b62c74"},{"version":"26.2.0-alpha03","sha1":"a76c1bc5559d447c746130831140593b42d305e6"},{"version":"26.2.0-alpha04","sha1":"21e4e969afa8940a30cf4337184df93ace1830d8"},{"version":"26.2.0-alpha05","sha1":"6926ad19b6e3f92a9b533812eeb441a6a728bf35"},{"version":"26.2.0-alpha06","sha1":"95cb1bcf01c89cdbb6a8e01c7bca2a487ba72991"},{"version":"26.2.0-alpha07","sha1":"df6227ca73a302ae3a95c74c6b59804eaef30a91"},{"version":"26.2.0-alpha08","sha1":"4f3ffdb952b938ea4b7255e17acaf8873a7104c"},{"version":"26.2.0-alpha09","sha1":"e5bdac0cc6e66eb296ff36e8c9577a69c4cb4198"},{"version":"26.2.0-alpha10","sha1":"d4d86b9a954f7674efdcc7b46179149807b784b3"},{"version":"26.2.0-alpha11","sha1":"e2868e959c33ea6e515be93036e7d94989bb4800"},{"version":"26.2.0-alpha12","sha1":"78cd1626c0e975bcbf80305512c2c17b0290b1a6"},{"version":"26.2.0-alpha13","sha1":"5153cc0b66a672d63719674a78a46e62e6d88445"},{"version":"26.2.0-alpha14","sha1":"8aa69bde56d3ddab91d2c4d3377216b1e05fe0f9"},{"version":"26.2.0-alpha15","sha1":"2c34aef888e90abfc55dbdfe3e5e0b7566574499"},{"version":"26.2.0-alpha16","sha1":"e7ab7a1bea899f8888987777ac5f109f70a185da"},{"version":"26.2.0-alpha17","sha1":"25ecbbbf5aab106a7c64361af41a472c6662cd12"},{"version":"26.2.0-alpha18","sha1":"421a735413cd7b2488600a8ea571e1eac233aec8"},{"version":"26.2.0-beta01","sha1":"f75ea9639b43c1ba85ed09c4470148b0a07713f0"},{"version":"26.2.0-beta02","sha1":"39e2d2590636e8def7906caea40cfdcb2fae990b"},{"version":"26.2.0-beta03","sha1":"37ef14dd02461accd3b91d75e6658c78deeef002"},{"version":"26.2.0-beta04","sha1":"23b6e68f170d11ae5973441904d459dc4d65c412"},{"version":"26.2.0-beta05","sha1":"ca50e4a4930927c207e8fb7c8a0cf5d319c29f09"},{"version":"26.2.0-rc01","sha1":"147ec1eb514feae1ba7cbd36dfe4c54b0379f5e6"},{"version":"26.2.0-rc02","sha1":"8e3194b1404e5595bc5a7347049eb7da4b463502"},{"version":"26.2.0-rc03","sha1":"b443d6ebe6060d7fb3caa37584357d9132fc8628"},{"version":"26.2.0","sha1":"1dfeadbb2aaf30ec711b1cf93a7f2c486c7ba8bb"},{"version":"26.2.1","sha1":"48562f79d01023de8746390e894fee47163fead4"},{"version":"26.3.0-alpha01","sha1":"b5295e23a208a27e44f8202335b27f3139b460e4"},{"version":"26.3.0-alpha02","sha1":"97bc9567771b92667d1179b605bb6099b067945d"},{"version":"26.3.0-alpha03","sha1":"606fa6d168fb0d2bfa6ddce6de3e85c48946401d"},{"version":"26.3.0-alpha04","sha1":"6a57506959acd0cef30957dce4c63c0506139272"},{"version":"26.3.0-alpha05","sha1":"77adfde49ad86f80932b8cd2cf137231c94cfb87"},{"version":"26.3.0-alpha06","sha1":"3d55c162c25f56f728b2cd6ef0a99a0bad5c3607"},{"version":"26.3.0-alpha07","sha1":"a122d2b137f1e99f963bd40a7b79d31b9c731eb1"},{"version":"26.3.0-alpha08","sha1":"3791cc8acafeba0816934f48f8583268529acee3"},{"version":"26.3.0-alpha09","sha1":"170621fea13f9e143d08eb7ed122c96789396e6a"},{"version":"26.3.0-alpha10","sha1":"8f83ab57cd373304646c6173692fd13c9e5e2c9c"},{"version":"26.3.0-alpha11","sha1":"53ffe2050492b648b977d44cfcbae72f0414380c"},{"version":"26.3.0-alpha12","sha1":"3cd02f67c073201aadb4737097598fbd6451ca89"},{"version":"26.3.0-alpha13","sha1":"df020c4b765343a936d34dc8397a3041c5de9e87"},{"version":"26.3.0-beta01","sha1":"936ecb4e0ddb8b9f15bae393ce8bfa792a68b227"},{"version":"26.3.0-beta02","sha1":"55371917ead5d4294c48d71f1992817bd4005545"},{"version":"26.3.0-beta03","sha1":"fed2c52077aa027c7205ceafee15035a0b35bd90"},{"version":"26.3.0-beta04","sha1":"a42dc8a2b3b7b5ca7d7d4ed544e97e849b3e1bcd"},{"version":"26.3.0-rc01","sha1":"afac44d5bdcda45876d79bf5eeb7f9c8f05d6e9"},{"version":"26.3.0-rc02","sha1":"19b244f01e73e283efc00684f1f591fe6a4b744f"},{"version":"26.3.0-rc03","sha1":"d31b55e50ffdf614cb82bf9f6f315aa17341a516"},{"version":"26.3.0","sha1":"c8e9a5b9f39334d5c9ee8423cb5d6a1d31d7dd61"},{"version":"26.3.1","sha1":"32358ac77a47d454399ff487b08892931ade0814"},{"version":"26.3.2","sha1":"7743aa777bad9a6dbcc6954f35ee6ed6a185014e"},{"version":"26.4.0-alpha01","sha1":"8d01f1319d4af2a1926c06374e89ecd77ceb4502"},{"version":"26.4.0-alpha02","sha1":"8fd12f0115497522785a725a1e768664e7b4c38e"},{"version":"26.4.0-alpha03","sha1":"c100231522e3e5d5369847c597b3b39743c10c6e"},{"version":"26.4.0-alpha04","sha1":"89ad77a5ef3f8ed9ebc3f332060cf5f7833ae371"},{"version":"26.4.0-alpha05","sha1":"6a6b80a2545c5d131ab6ec05aced14c3104d7b22"},{"version":"26.4.0-alpha06","sha1":"64e12211265958f98b89c0ef379b033c8d1a7bf7"},{"version":"26.4.0-alpha07","sha1":"31072d1b49d4dba4e251360baeae2e694b148a75"},{"version":"26.4.0-alpha08","sha1":"863e08154aac63e67368a884d2e49880e4b899ac"},{"version":"26.4.0-alpha09","sha1":"9a94484c2b609260b0c0c9ef1bd180fe77d9e485"},{"version":"26.4.0-alpha10","sha1":"5208872e3b3836dba50203d19865aec2607a521f"},{"version":"26.4.0-beta01","sha1":"2b45476b9b7e61959ce19f3a263a9640aaccb8ec"},{"version":"26.4.0-beta02","sha1":"9b8d476b29892ca4788bd568a8182e07aedbef2d"},{"version":"26.4.0-beta03","sha1":"c4e157f4fd0876731739134280e1362b1835aacf"},{"version":"26.4.0-beta04","sha1":"421ed680b82528cb22fe671f3d90057d6a0fc2b9"},{"version":"26.4.0-beta05","sha1":"99f6b14eb5a116916701edb36fdf69583e4e8f6c"},{"version":"26.4.0-rc01","sha1":"ccbac16932cab6bcec7b233a6780cd88ef2fb6ac"},{"version":"26.4.0-rc02","sha1":"940b3e18c709329a7e3b6afe52f83bbbea0d0073"},{"version":"26.4.0-rc03","sha1":"bfb11ddf422eef0c135fb19318bc82975161b015"},{"version":"26.4.0","sha1":"b144cd14b00de2b8f95b0ec0a5b49d57131ab89"},{"version":"26.4.1","sha1":"2f877d8152f90000fd66a7c899b5f09a46933cc"},{"version":"26.4.2","sha1":"2b4a0183ac9517f6276454075975bf8ccb2d3e37"},{"version":"26.5.0-alpha01","sha1":"f3d33c8258a6cd5fedd311c895f93f99d1174927"},{"version":"26.5.0-alpha02","sha1":"e29bb7fa73bf8d6259c2a49a784499317759933e"},{"version":"26.5.0-alpha03","sha1":"b0a386d592934af1abc9e799c40704c352e7d8df"},{"version":"26.5.0-alpha04","sha1":"2501036b16a224b2f0d23804519cce9aade86017"},{"version":"26.5.0-alpha05","sha1":"73235c818599e5cf6ee813d5d9ded21b8b706ce8"},{"version":"26.5.0-alpha06","sha1":"1419785bb12682ecde0f995fa175ece3eb8ef6a0"},{"version":"26.5.0-alpha07","sha1":"7ede5b5fa2daee63bc37d7cc5835ee8065cda680"},{"version":"26.5.0-alpha08","sha1":"737112557c631861be9a865cc894c6ab071670c9"},{"version":"26.5.0-alpha09","sha1":"e8cb4582bbe2cbb7b47fd36c3e9ad4476fadfd6c"},{"version":"26.5.0-alpha10","sha1":"cb19a6b69c7b8186fa0c293d6862b731534b0c4"},{"version":"26.5.0-alpha11","sha1":"d72c6c1a88e742f92dc8c64ebe99d74287b6870"},{"version":"26.5.0-alpha12","sha1":"142027f624119afcab19ecb6bb9e0c591f138224"},{"version":"26.5.0-alpha13","sha1":"fa0a881a9a9e85fd0460650c759a9c240c37c29a"},{"version":"26.5.0-beta01","sha1":"83159f4569959eff4033dcb9e968f1cc0a1f678b"},{"version":"26.5.0-beta02","sha1":"b7258a0828740b9f760ccdc072734b279036e267"},{"version":"26.5.0-beta03","sha1":"2756811b6d4fb8d6516f5388a0017f1134383d66"},{"version":"26.5.0-beta04","sha1":"5ced2b0af6a1dd7ed8fb85a88a31ca72cebb16de"},{"version":"26.5.0-beta05","sha1":"ef6efa7b6cdd0db9b1dc72d8b56b11373fac74b1"},{"version":"26.6.0-alpha01","sha1":"8b5570692ef4fc44a632794250dfc8dbccef4446"},{"version":"26.6.0-alpha02","sha1":"16233b045385edcf5937b811d92e18e9dc33ab4c"},{"version":"26.6.0-alpha03","sha1":"ae127b091862ebac5f23f40125330cd19efaa8dc"},{"version":"26.6.0-alpha04","sha1":"bc95c1ab8021c41a12cec8177fa9190d3ca8c052"}]},{"package":"lint-gradle","versions":[{"version":"26.1.0-alpha01","sha1":"5602e7ea154940078ff21f9f12bdce2a25097caa"},{"version":"26.1.0-alpha02","sha1":"203f7c069b330b021b4e684039552e658585c160"},{"version":"26.1.0-alpha03","sha1":"ae4a05ff53b5d38aa0b620256dd6a7f2a23e62c3"},{"version":"26.1.0-alpha04","sha1":"dabac212d5ece34caa5cb004b89499c7ce2e86f0"},{"version":"26.1.0-alpha05","sha1":"48d8a18c50200412a7a9aca08ccd1a15efcacf0"},{"version":"26.1.0-alpha06","sha1":"c294a443155f53782a3eb3cf41c1fc31d35b1701"},{"version":"26.1.0-alpha07","sha1":"145c4056dd137d7969f82a3630686689f7bce3e1"},{"version":"26.1.0-alpha08","sha1":"a5d3d8aac737c23d532985b6585439343de52f3b"},{"version":"26.1.0-alpha09","sha1":"1398e84ba6600c68a615b28e775b4e5ac249808c"},{"version":"26.1.0-beta1","sha1":"920b8e24dfa78251a9d40029c3d2efb636f3c50b"},{"version":"26.1.0-beta2","sha1":"e5bd98d16be779095690f03261afb5a39d707eec"},{"version":"26.1.0-beta3","sha1":"d7902820f231ffdb565ae47c4f105f13f27051b8"},{"version":"26.1.0-beta4","sha1":"ac7769c4ba8f9a7446d0116a296883b90d4e7451"},{"version":"26.1.0-rc01","sha1":"48033bfaf3c26bd5ffff6f75afc6b6164799728d"},{"version":"26.1.0-rc02","sha1":"ae90a0e311f9da8df7a6e0c957d60e9f201a2e44"},{"version":"26.1.0-rc03","sha1":"b45dd8109d3196f481eea18c0a601764c82fe86c"},{"version":"26.1.0","sha1":"9cd81fbca72b9a5498d3cfe2418dbc962d5834d"},{"version":"26.1.1","sha1":"5a844e1fd03775609c7479a886c9fb7a2d9f9a7b"},{"version":"26.1.2","sha1":"65e43ba47b79a05e9848d04c49abeb8417780af8"},{"version":"26.1.3","sha1":"6366ea47b6475b4e51ca8768668329a5afa4b245"},{"version":"26.1.4","sha1":"f8969848ceda7fb7ca3ce07c3c5e46a35a824b05"},{"version":"26.2.0-alpha01","sha1":"5af8f3b1822ac7086c3846c7f363cf733eda10cc"},{"version":"26.2.0-alpha02","sha1":"4554fd38ccf2a724b798999874df00aa640a2e63"},{"version":"26.2.0-alpha03","sha1":"e60652480385f4145c81a2ea2c3b0de2f0c2b120"},{"version":"26.2.0-alpha04","sha1":"b196b172ae4302b237bcad1791efbef0fcfcfb72"},{"version":"26.2.0-alpha05","sha1":"67d275e2fb756beee39c0b8492603daaca266070"},{"version":"26.2.0-alpha06","sha1":"99fca06120f6b367da1ccfc3442234fd864f9ec4"},{"version":"26.2.0-alpha07","sha1":"e268b9aabc32eaa8fb4291a09458f9c88c554cef"},{"version":"26.2.0-alpha08","sha1":"9506a8fa3e6c8803ae99dde6eacac07f41c9dedb"},{"version":"26.2.0-alpha09","sha1":"fafd801d9e3b227aa717c5380f17e188778e1c90"},{"version":"26.2.0-alpha10","sha1":"d325d5031a6144f0c3465a992708ea78e2803e92"},{"version":"26.2.0-alpha11","sha1":"5975b7784ceb1a713de699828510ba47f381b8e5"},{"version":"26.2.0-alpha12","sha1":"b8954ffda0b2ae5e9594d7664df31acca1334f66"},{"version":"26.2.0-alpha13","sha1":"8c219f16ce6fb2603924832fe02585f928f82349"},{"version":"26.2.0-alpha14","sha1":"583858a884d92b4620eca45b2a754819e4333d2"},{"version":"26.2.0-alpha15","sha1":"c6ebb7d4ac7df001e121e0d1ee3529731cda6e05"},{"version":"26.2.0-alpha16","sha1":"7bd4f2e6b53d520294c07ee340179bf380d3b70f"},{"version":"26.2.0-alpha17","sha1":"9e77fe3e669804ba4cef358048f93f3061576fd4"},{"version":"26.2.0-alpha18","sha1":"50ab35e9950436affb1a8d79e92907cb1263766c"},{"version":"26.2.0-beta01","sha1":"7719e6061941ebe3d50505e003a43833aa302997"},{"version":"26.2.0-beta02","sha1":"f2a9264a14c6980d017778f4648ceee049689128"},{"version":"26.2.0-beta03","sha1":"4b4bb6b094c30602874eb91df072d12901ae16af"},{"version":"26.2.0-beta04","sha1":"86ed6cae90395fa1335aeb091396b2ec5eb5d21d"},{"version":"26.2.0-beta05","sha1":"7391f76fd14ca04be1af8930b00f3b98bcf6d1f0"},{"version":"26.2.0-rc01","sha1":"bc352ed642e8c752d6c9d91d820c9d6ea87378ec"},{"version":"26.2.0-rc02","sha1":"d6881a9f044042d8999cb512c798f6a02945138"},{"version":"26.2.0-rc03","sha1":"1e96e1c3f189daf4707c2cbf460ea54b0e58e6c0"},{"version":"26.2.0","sha1":"77758d10a6f53cf39eaab0cbe525b687ba2d5393"},{"version":"26.2.1","sha1":"4e87889b8a765c804bed51ad817e9a53ed50966f"},{"version":"26.3.0-alpha01","sha1":"5be31c763e319ddb6da845df691e3feba00f5063"},{"version":"26.3.0-alpha02","sha1":"b4f112f67217e64ace394a0038a6be6cd9228c84"},{"version":"26.3.0-alpha03","sha1":"76fa542acbd970ac5638e25fd08dc6f1e39b7213"},{"version":"26.3.0-alpha04","sha1":"f49d21155a8354bd27389b4d87abe062e0c739b5"},{"version":"26.3.0-alpha05","sha1":"624b46c3bdd027230fc97b188c737e1e9bd17e4a"},{"version":"26.3.0-alpha06","sha1":"dc0562ee7693abb7b23406e2652eb59d21ca32d1"},{"version":"26.3.0-alpha07","sha1":"f0e64570d17afbeecaeb346a1d5d05df16d4d173"},{"version":"26.3.0-alpha08","sha1":"6ea80d9920cef09cddabe8bf51b83171f518de83"},{"version":"26.3.0-alpha09","sha1":"e4ccd2e4306ad777ea73977506d5dd2cc1aa7d07"},{"version":"26.3.0-alpha10","sha1":"311ae5c1a31cd1d761a6091b02d62be8b97b70c5"},{"version":"26.3.0-alpha11","sha1":"6920d4f883907a191df95015306170e3cf3ed784"},{"version":"26.3.0-alpha12","sha1":"d740f663164e8d830cd5805e98ceee740072062b"},{"version":"26.3.0-alpha13","sha1":"f5c78632091f957f378a3a8dfbfe0c223f05b652"},{"version":"26.3.0-beta01","sha1":"fc9f656f940ac3193fe6e02e6ae9968a03763563"},{"version":"26.3.0-beta02","sha1":"7ffbdd766090ed2104a204d17f03bcdeba03ae10"},{"version":"26.3.0-beta03","sha1":"fbb314d1ce6a32f8e9b288321436ac0651d789d2"},{"version":"26.3.0-beta04","sha1":"b28d7fdb4667484bfcceaf1ebcf0ad09174d3080"},{"version":"26.3.0-rc01","sha1":"16b1273d9bc8c93c7bf2358cdc4cbd73fd75c447"},{"version":"26.3.0-rc02","sha1":"6185d801c8754e11bb4638e89624a84fc2c9ae3c"},{"version":"26.3.0-rc03","sha1":"1c404d1a9c8fc3d80365935e6f350179fa611d77"},{"version":"26.3.0","sha1":"788b5b5266cbffd7fbeecfd73a01f1ba0130df6a"},{"version":"26.3.1","sha1":"18909bd9dcf095d02df647f68f2d2e4a1422d316"},{"version":"26.3.2","sha1":"9e88bd5de70ef99d3d5f265c72b3f10f4095a6f9"},{"version":"26.4.0-alpha01","sha1":"5e03526e18fa605a19fba6040b6e0205d8ebf31"},{"version":"26.4.0-alpha02","sha1":"1c7740bdfbe6384eaf35adfc543896e343934fef"},{"version":"26.4.0-alpha03","sha1":"8de1aa26a77df08b768ef196a7f2335d65ecb9f0"},{"version":"26.4.0-alpha04","sha1":"898dd9e4c2fd76f80ac65cc27a404333b3425e21"},{"version":"26.4.0-alpha05","sha1":"83ccfd38c94359c1fbc1cbfa994a5dd7bdb8672a"},{"version":"26.4.0-alpha06","sha1":"ff28d5b4545b02a4dd92c3aaa0309a2d921fbe6e"},{"version":"26.4.0-alpha07","sha1":"dfe848ad86aa1c0175642cc5302d1a25278e1511"},{"version":"26.4.0-alpha08","sha1":"e16e4fa36087725d871f663c949f348238eeeb43"},{"version":"26.4.0-alpha09","sha1":"f16b38d01c181d41841e6587f2bfa3752aaa53dd"},{"version":"26.4.0-alpha10","sha1":"8da369acc5ec8dc74e2c2573f1f36132362c628"},{"version":"26.4.0-beta01","sha1":"e7353408b9c13f6817a25a2f04f509c5b36b469e"},{"version":"26.4.0-beta02","sha1":"6e5710179ecc705e965cda9cc4b46ba2710fa800"},{"version":"26.4.0-beta03","sha1":"a752f06f015fc56c44cc147e923d146c74884305"},{"version":"26.4.0-beta04","sha1":"776cdfe5286c31298e98be4adf4223555149b2bf"},{"version":"26.4.0-beta05","sha1":"7fce90b4d172412a0248be82be29677c93af4de7"},{"version":"26.4.0-rc01","sha1":"6010d598383dc49c66d7d65ba52597fa1170f536"},{"version":"26.4.0-rc02","sha1":"4992b394bf906dedde71a8215b8eca83dd1bce4e"},{"version":"26.4.0-rc03","sha1":"939f771f93cbfcf137ea8301ba57be05b6ecc07c"},{"version":"26.4.0","sha1":"37a4329b9cba9f4c316cb084ca74da988cee7225"},{"version":"26.4.1","sha1":"1e5e557e9bc3cbd4a0559d5c076b923c61eb905c"},{"version":"26.4.2","sha1":"8c4b7cad40bc01adaab90d43ea81e4b538626bf2"},{"version":"26.5.0-alpha01","sha1":"560648698412ca66f6c92267d758a8dae3be9c95"},{"version":"26.5.0-alpha02","sha1":"dd6248543f23a0e3d7ebbf9287df7715235f4d26"},{"version":"26.5.0-alpha03","sha1":"3d701882679fdf54db0832bd7eaa49c1bca377c9"},{"version":"26.5.0-alpha04","sha1":"1daf4116d7e1a48cb476d2801e9d4b2bf3c089db"},{"version":"26.5.0-alpha05","sha1":"661c19fe15c652f515fbb8a0fe74b62d153c612d"},{"version":"26.5.0-alpha06","sha1":"e44c38fe2feef54730000bd8142d2ad04195bd93"},{"version":"26.5.0-alpha07","sha1":"2c74d42a17817965c92925cb304c9a6888e1256c"},{"version":"26.5.0-alpha08","sha1":"d64766d0fe9982912c015c6863b56f96ca286db5"},{"version":"26.5.0-alpha09","sha1":"3816b95e78dbc3a13ae2b7778d04693b2e930166"},{"version":"26.5.0-alpha10","sha1":"7b9718fca55f33ee2c8ff4f8045ad59cb1633d17"},{"version":"26.5.0-alpha11","sha1":"f3c6f994db40458dbcba379d5a4609d977eca773"},{"version":"26.5.0-alpha12","sha1":"ff681c55e63ac9fd01941d109ff181aced5e406d"},{"version":"26.5.0-alpha13","sha1":"2d50254db38f4090d271720b62fe7afbf1d0375b"},{"version":"26.5.0-beta01","sha1":"fa0c7bf3e2fcec8eea23984094b83814e71a05b"},{"version":"26.5.0-beta02","sha1":"25272d8d993289aeced0fdfed9f5966b36706271"},{"version":"26.5.0-beta03","sha1":"2f30e9ecd0f895a70d3446a840c0cc232b11a110"},{"version":"26.5.0-beta04","sha1":"ea208eb998a8aed7903b09985edf55db79e942de"},{"version":"26.5.0-beta05","sha1":"87bf6d4f9384c8372c8878f54cbd5faac774e110"},{"version":"26.6.0-alpha01","sha1":"b075d3696c64b35a32523574bf348210e9fc8070"},{"version":"26.6.0-alpha02","sha1":"e32a662b92f824cae6e63e59de581cc86bc3afda"},{"version":"26.6.0-alpha03","sha1":"daa25bf822efabba9e76072c5074b7dc7e2ba65a"},{"version":"26.6.0-alpha04","sha1":"5c04d18dd4a4c59761b26086a01e4e6730968e55"}]},{"package":"lint-gradle-api","versions":[{"version":"26.1.0-alpha01","sha1":"53cacd6c8e9a739a7878cde6ccf9ac69a65fe12d"},{"version":"26.1.0-alpha02","sha1":"834504672843eb2ddaede0092da1182e148cf1c7"},{"version":"26.1.0-alpha03","sha1":"b8787a5f23e0b07257864d90a6069c1318da840d"},{"version":"26.1.0-alpha04","sha1":"10ee5d024eef9af86311493bd2758f411f709a44"},{"version":"26.1.0-alpha05","sha1":"d5f9a0c357a0c5bd113c4d0e5ae3f5409fb57f52"},{"version":"26.1.0-alpha06","sha1":"b192853b34d5d485509b969d1e5c5b4db48b905f"},{"version":"26.1.0-alpha07","sha1":"be6cf1a20258045f3b01c2bd253d940296fa875e"},{"version":"26.1.0-alpha08","sha1":"5669f05c1cd1522a0100a2fdbe3803fad5f0290"},{"version":"26.1.0-alpha09","sha1":"12aec653d55149064119044cd4fcbde6418ea005"},{"version":"26.1.0-beta1","sha1":"f62227e058fecdb2d49a5eb9708ae6b2596b7c74"},{"version":"26.1.0-beta2","sha1":"a290e1aa759e9f1cd866ff5bf60908176d37e534"},{"version":"26.1.0-beta3","sha1":"466e0c667c75e5ed54874d1fe40f333c3494d846"},{"version":"26.1.0-beta4","sha1":"fc29aeae629183fb718ff4230427efcd7b8cd75c"},{"version":"26.1.0-rc01","sha1":"1ad2110cc524fe7a0adce6828fc96c3b5f3a60f0"},{"version":"26.1.0-rc02","sha1":"eb188b9d75529c891e86516b606cc45a8df4e650"},{"version":"26.1.0-rc03","sha1":"98873f03b345889a66feb3466680497fbfd7fa54"},{"version":"26.1.0","sha1":"cc007f9cd3a1ee3720e7f003d5744f0a5485014d"},{"version":"26.1.1","sha1":"8201b10c8d03c2319dfc14afe5668c64ae05d5bf"},{"version":"26.1.2","sha1":"8c54aedfe9da66e64402de04883cee083c127a3b"},{"version":"26.1.3","sha1":"8ad178b12806cff1b96b7d471a46d56dfd775844"},{"version":"26.1.4","sha1":"cbc00782604b7d0ad50e9c50b84b074af79394f0"},{"version":"26.2.0-alpha01","sha1":"9ff1f7b12803d0c4bc85f4b5f0cccde615956b77"},{"version":"26.2.0-alpha02","sha1":"e75848e41c4960a9856d5897768ac8cf7779cbdc"},{"version":"26.2.0-alpha03","sha1":"333a7acb6028bf88b52fa799c04538b068f71659"},{"version":"26.2.0-alpha04","sha1":"9941220fa4990fac9dce7196a389b3c22ff586ea"},{"version":"26.2.0-alpha05","sha1":"657e45a2e189caa561c410b9b507d418ec681ad7"},{"version":"26.2.0-alpha06","sha1":"c5d5cc82603609fae79e9cf0369a979377c1f80d"},{"version":"26.2.0-alpha07","sha1":"752199b2f072572d66d7450616bd855339e3156b"},{"version":"26.2.0-alpha08","sha1":"cf26d6d714038adc6460c1918f50c92ee85399a"},{"version":"26.2.0-alpha09","sha1":"dd80be2800ebde6acb2bd3067b133f6a20bb74e"},{"version":"26.2.0-alpha10","sha1":"903b9d6996c9fab76934e6e413bacd09f4e9533d"},{"version":"26.2.0-alpha11","sha1":"e99f38fb3f8ff57b86e24cec267be28873fde54e"},{"version":"26.2.0-alpha12","sha1":"5c05ac16e3b5e0348cf14993204a078723fdc3ba"},{"version":"26.2.0-alpha13","sha1":"e5bf7ae1965b0bc33b92355dfb8ec38b5d3109f2"},{"version":"26.2.0-alpha14","sha1":"36ab0c6f277fe9e3d6ad822723963d8faa0add1a"},{"version":"26.2.0-alpha15","sha1":"d8d39a8b744d28cd4395fe34f8feaea7e42a33b"},{"version":"26.2.0-alpha16","sha1":"8cd023bf807d98f05f508b6509d4cf6037d059c0"},{"version":"26.2.0-alpha17","sha1":"86421b05ce0806ad7a7729ac2cb27cbf16b23ca4"},{"version":"26.2.0-alpha18","sha1":"3f43928e026d03b79b2dc5d8ceae52b4252fca7a"},{"version":"26.2.0-beta01","sha1":"84fe06de8f799869053566e577d3fbb66bc2ff99"},{"version":"26.2.0-beta02","sha1":"ff4c1708630b57e0208efff8bb910e841caf67d"},{"version":"26.2.0-beta03","sha1":"e9e6481164f8351b5e49e7006ffa5d059dd0824e"},{"version":"26.2.0-beta04","sha1":"b81fc00f328b460366c1dd15840b9fd7b81d0093"},{"version":"26.2.0-beta05","sha1":"649f92e3f465207f6baa8d0a42f9d8a95d1726b"},{"version":"26.2.0-rc01","sha1":"8238eb852d4a768c2080d17e610545f14174352c"},{"version":"26.2.0-rc02","sha1":"c9cbba234e4ab12ef3c2c99af207dede6b2d47be"},{"version":"26.2.0-rc03","sha1":"c12aa2dbc90e4bfa83359b7114998783859fea2b"},{"version":"26.2.0","sha1":"5f3065e0847ff1a7261905a26ca09e01ac159b9c"},{"version":"26.2.1","sha1":"62f8362369c570266bcd4f030908ec1d237df331"},{"version":"26.3.0-alpha01","sha1":"921559f3379534fc1d9b418c0e9734e1886cda29"},{"version":"26.3.0-alpha02","sha1":"c15bb2d2ae880303e69ef9e8f866ebc1aa249696"},{"version":"26.3.0-alpha03","sha1":"533d5e22334c49b8e07a05f4c2b76e39e0fee451"},{"version":"26.3.0-alpha04","sha1":"e8e57622ad131704cc19024175e53c7a1c1b0a98"},{"version":"26.3.0-alpha05","sha1":"62aae3b82852360e108f7fa23bd06dd7960cc117"},{"version":"26.3.0-alpha06","sha1":"e00ea7c328660c83703d1e19d022ec0a71afada6"},{"version":"26.3.0-alpha07","sha1":"24f82ec22d97cbd10f7f5ad2f31a9a48f928a5da"},{"version":"26.3.0-alpha08","sha1":"dbc4285d267712c7a52548e85b6ff68a6e58f8a8"},{"version":"26.3.0-alpha09","sha1":"492246a7b8238945a7b6f59125cdfea553321fdf"},{"version":"26.3.0-alpha10","sha1":"df29689675d7e6963481c37b8be3bbb4e049a421"},{"version":"26.3.0-alpha11","sha1":"b45461a4298a334b66ff592ca8b5c8ee9c0e81e1"},{"version":"26.3.0-alpha12","sha1":"e30878663951f6597e6a1b929ee09c894878e2b0"},{"version":"26.3.0-alpha13","sha1":"43f4898e05275572405697c3b23c953410adf658"},{"version":"26.3.0-beta01","sha1":"b76c4ccc080605eded0462ce3352760723cab73e"},{"version":"26.3.0-beta02","sha1":"960f0bae7e7d0e48317fd814658a985300d53bc"},{"version":"26.3.0-beta03","sha1":"aef20cb857d093a44d09eb81bb74afe05951d4c1"},{"version":"26.3.0-beta04","sha1":"748cf150f23c9377a13284e05d69d9904fb56837"},{"version":"26.3.0-rc01","sha1":"968adb71fc751639b46d31f0e66a7ab43275bd6"},{"version":"26.3.0-rc02","sha1":"106f9afda4a337dcc29b853a92014e28a8da6f4c"},{"version":"26.3.0-rc03","sha1":"74e5bdcc891c605461cc604e24be4b9809bed619"},{"version":"26.3.0","sha1":"4da81983913826ba3c713c1647f3be760618e02c"},{"version":"26.3.1","sha1":"d55e1bcbe4e7c0bb97f5c385a003c4339e38d07f"},{"version":"26.3.2","sha1":"bfae60b60cffb45f06a360899eedf6ee9acc06b9"},{"version":"26.4.0-alpha01","sha1":"5e5c99577e4fa23b0a661ecb590892be9c1b8e6e"},{"version":"26.4.0-alpha02","sha1":"eb5afef772b066afeaede9cf2b7161f14d144c12"},{"version":"26.4.0-alpha03","sha1":"9f6a379a85f3d0ba50a84d4ffc1dfa624aaba166"},{"version":"26.4.0-alpha04","sha1":"d892a65657ecd692c6be27786d8f964f9974483c"},{"version":"26.4.0-alpha05","sha1":"b704dbf609f77d3604b988ebd5b056cc4152d738"},{"version":"26.4.0-alpha06","sha1":"99ea6f486cfae42e912e22a0d29c65c8f55ec66"},{"version":"26.4.0-alpha07","sha1":"bfe6d02fb6dac48a0a0649fa71cb01ab49093839"},{"version":"26.4.0-alpha08","sha1":"519d639fb5c438cb1309fb6d72123b67921ab470"},{"version":"26.4.0-alpha09","sha1":"99fd280bbfa1dd76e890b4ac3c105392d4da23ba"},{"version":"26.4.0-alpha10","sha1":"e6ace8790a6a84e783db77dcf6810364a0f53c67"},{"version":"26.4.0-beta01","sha1":"ac9ef62fd638c07407cd4c750ca78f7c721c5e35"},{"version":"26.4.0-beta02","sha1":"c4ce146b5eeaaab0bdcd5569b826d65b220b360d"},{"version":"26.4.0-beta03","sha1":"a6947529fef1a3bc4e3e2562bb82a7b377f3ba26"},{"version":"26.4.0-beta04","sha1":"20fc06a54f871ce81d9cfce8dfe27fd6c86605a3"},{"version":"26.4.0-beta05","sha1":"2bded5996eb255bf286c7bccc037e3514bf1f615"},{"version":"26.4.0-rc01","sha1":"7809576223ba7793a75487dc887e0b63e08a5fd4"},{"version":"26.4.0-rc02","sha1":"6ee49e208b4c0e4f97bb5652a7e9b491e54b7552"},{"version":"26.4.0-rc03","sha1":"6bafb1c1879ff68b6c3ef254cc1e6a02df524184"},{"version":"26.4.0","sha1":"931c6d23146c4d8286aa814eb8d5d8af9b69af11"},{"version":"26.4.1","sha1":"f9dc1f1fba2b9b51f04e42497af0932119ea8c9b"},{"version":"26.4.2","sha1":"b9f722c4faec2a997fa1d2e97e14019554241200"},{"version":"26.5.0-alpha01","sha1":"fdba631d7cedda71e0eeb0380f2dbf8a3103528b"},{"version":"26.5.0-alpha02","sha1":"fa267da2727e6f737e1bff9536c435d9e577e2ec"},{"version":"26.5.0-alpha03","sha1":"274b5309dd857feb9b379377b5249ae7d4d645ed"},{"version":"26.5.0-alpha04","sha1":"7a3198753d9fcf4954354d383d79123fc3b24f4c"},{"version":"26.5.0-alpha05","sha1":"c05b1345b9c1c8039724f6f1fb52cb24727474ce"},{"version":"26.5.0-alpha06","sha1":"d455fcce13d7064da83fd25a3ed858b0fd3f24e6"},{"version":"26.5.0-alpha07","sha1":"95481ba3e2ce94a41fbae0e1e6103fa06ec85548"},{"version":"26.5.0-alpha08","sha1":"ec78d006ccdd7a2faeb67837881baff71bd03acd"},{"version":"26.5.0-alpha09","sha1":"16f6397e4614593d369cc32e3b11049cff160110"},{"version":"26.5.0-alpha10","sha1":"264278389ba6a276a1442638d08c357f3dd8e79c"},{"version":"26.5.0-alpha11","sha1":"e16eed4bad8aede2f235717b0e18ca9ee803349a"},{"version":"26.5.0-alpha12","sha1":"44939e1f1a9bedab07b41df7e55fbf661e29c365"},{"version":"26.5.0-alpha13","sha1":"882671f7d2f6dd9d6a0431d6c5fb68f7691055fa"},{"version":"26.5.0-beta01","sha1":"94d52bc2b7cff9d68c4d58bead3e8a3b57c3ec21"},{"version":"26.5.0-beta02","sha1":"15334f05b0a9b4d1d474f4390d8fab11f3407aa2"},{"version":"26.5.0-beta03","sha1":"20c438b80504fceaf8127826203e61d62a9d00cf"},{"version":"26.5.0-beta04","sha1":"d4af19fe6691478d65e0754ca466441b02134874"},{"version":"26.5.0-beta05","sha1":"69f1ff8557812fe8470b500a95c32ebb6c900a7"},{"version":"26.6.0-alpha01","sha1":"a5e7a44d12c0345e0de75b5443811e61d01da826"},{"version":"26.6.0-alpha02","sha1":"7d05b1811c6ba6c538bcb3528aa6b231642ee404"},{"version":"26.6.0-alpha03","sha1":"5be4450bdb6518a392c42a6453d4093b5a482bbb"},{"version":"26.6.0-alpha04","sha1":"ed352b36ad2c71c9e1040a1426902d10fae87ec8"}]},{"package":"lint-kotlin","versions":[{"version":"26.1.0-alpha02","sha1":"f0f62504f3df9de2a98507fba389b48ad8789681"},{"version":"26.1.0-alpha03","sha1":"6f4f7b4578bc3ff3d335464201ac1016e9e93b30"},{"version":"26.1.0-alpha04","sha1":"8e36a7b5af78d18ae08d5d562e8353c6b8af116c"},{"version":"26.1.0-alpha05","sha1":"dedf8e3d7d3fa4690db856d4c2574c2d249a19ab"},{"version":"26.1.0-alpha06","sha1":"ea451a86d937fdb121d5b281e058378a903acad8"},{"version":"26.1.0-alpha07","sha1":"1a0f026c6b09f21c679e6c5f81237dc025058c22"},{"version":"26.1.0-alpha08","sha1":"f1bb27994c7e603b73abd2d7682a57cbe0f929b3"},{"version":"26.1.0-alpha09","sha1":"631c83ba67962ced91104c61ab77f12b52d9977d"},{"version":"26.1.0-beta1","sha1":"8aff2e00183cdcf9ba9c430e4765c5fa343125df"},{"version":"26.1.0-beta2","sha1":"24718b0787a314c755273066ae1d6484b9dd0457"},{"version":"26.1.0-beta3","sha1":"52ce51de95fe9485b1bdae20f466b0464fde00f2"},{"version":"26.1.0-beta4","sha1":"81bef4ab601234b7839980593ffc62a696434cde"},{"version":"26.1.0-rc01","sha1":"35d00a64ccd3c8d31f8b3975334dfa7157df2d7d"},{"version":"26.1.0-rc02","sha1":"b5957674bd28e77a23ffb9f9e95f80cfbc0b75c4"},{"version":"26.1.0-rc03","sha1":"ede970975c70288e683a6fe18aa98de4cf945550"},{"version":"26.1.0","sha1":"a38b881916dd6749921b4aa792a79ec0c8c0bea"},{"version":"26.1.1","sha1":"fa0e4a3195a66957e4cf527de124ec12b152f71a"},{"version":"26.1.2","sha1":"10cd8876c94b6ca93da4b1735330fc9269d13856"},{"version":"26.1.3","sha1":"d6a5dc8654d01170aaa34759ef4d73c849861553"},{"version":"26.1.4","sha1":"af8cf3dd17b561dae138467c118fd3a955cd10b4"},{"version":"26.2.0-alpha01","sha1":"d83736c24b6f9028a431932abdb2fe84b2d1b102"},{"version":"26.2.0-alpha02","sha1":"1431d485deaf71a07af720b68f3bd7b695784fa"},{"version":"26.2.0-alpha03","sha1":"1848a0900409d73e265529666b9fff78819ae842"},{"version":"26.2.0-alpha04","sha1":"15bec10ba251a251612969d8dadff0da55f3de4b"},{"version":"26.2.0-alpha05","sha1":"b49db26fb5b8b2ef5541699c0476126b6fc74cf2"},{"version":"26.2.0-alpha06","sha1":"455de70f139ab7c1fe1c8895f6c29112e4fbf3c"},{"version":"26.2.0-alpha07","sha1":"707ee58921849372c2e2593fff902fc84dfe4069"},{"version":"26.2.0-alpha08","sha1":"866d96f7ed9e62142f7974f71d1be696515828ea"},{"version":"26.2.0-alpha09","sha1":"78363a714c61fca0716028595fd3f36171ff5c8e"},{"version":"26.2.0-alpha10","sha1":"83a37512a5d248a123ff7a437409095e071f8898"},{"version":"26.2.0-alpha11","sha1":"d04c9f9294dab8117e9510f7de8d55de46566313"},{"version":"26.2.0-alpha12","sha1":"c2a7762e5f91d445ab697e4e24b3a171c95cae9f"},{"version":"26.2.0-alpha13","sha1":"7179b53cbc611ca1255f1de0109cef39fced2bae"},{"version":"26.2.0-alpha14","sha1":"6107ffe9eca4ded42d31917d11bfb03d479c9533"},{"version":"26.2.0-alpha15","sha1":"24258ce3072d223ac992726187463389f091240a"},{"version":"26.2.0-alpha16","sha1":"5af14e008f3aa77f361fb66c7d31e4d2135dbe6b"},{"version":"26.2.0-alpha17","sha1":"bc7f8c80b91f46a1b48cfb988667098253158a0f"},{"version":"26.2.0-alpha18","sha1":"d332834453c4fa95188b2eb79e3c785d97e59bca"},{"version":"26.2.0-beta01","sha1":"7b17bb277421106acc3eb9f73ad106746eeceb02"},{"version":"26.2.0-beta02","sha1":"1bb54c9352a6a542c78b2e2c1d45a0ae1e19d86d"},{"version":"26.2.0-beta03","sha1":"599aa105e190e108c887743c28158a1d19e0e69d"},{"version":"26.2.0-beta04","sha1":"e2f13650c008ff6e5dae57b5bff65cdb7babca72"},{"version":"26.2.0-beta05","sha1":"97c55948a7304ccb2891504300c09ba4ceabde3f"},{"version":"26.2.0-rc01","sha1":"6c4c4c032a6b2ae8a082e8ecb8e7d78cb1e159af"},{"version":"26.2.0-rc02","sha1":"58cac7303242df3c0760d8bfbde2676b6b512072"},{"version":"26.2.0-rc03","sha1":"fa69b8433d21c2a990585b9e5cb4142e1e7f5a4e"},{"version":"26.2.0","sha1":"4e9e4f2f59a6847c45adad6c577c654fa9474e0a"},{"version":"26.2.1","sha1":"14bda89dd851563372956731beebdfbbf3e0cf7d"},{"version":"26.3.0-alpha01","sha1":"b927039f8d3fefff7a8f2171ec53dc71b470909b"},{"version":"26.3.0-alpha02","sha1":"97534e58213474c0ed7fc8cc8e73f58c84125604"},{"version":"26.3.0-alpha03","sha1":"14e6dfa87323ef60d332ecade9a7710754c4009f"},{"version":"26.3.0-alpha04","sha1":"a90ab75e33dbeeea90942fd4c2bafcaab488d23e"},{"version":"26.3.0-alpha05","sha1":"e5170e6e442d2a8d397a63d2f0bf7fc916580c13"},{"version":"26.3.0-alpha06","sha1":"bee580392b9f83536f679e605faa0b6fddb44b50"},{"version":"26.3.0-alpha07","sha1":"a93a62a25d158356e9154df4f98c2ab2bf46b55f"},{"version":"26.3.0-alpha08","sha1":"7b50dc8b5877fbb81473bd800b601f4ee843a862"},{"version":"26.3.0-alpha09","sha1":"b8c5fa8fb71ef88a3b6cb734de6bd36d806538e7"},{"version":"26.3.0-alpha10","sha1":"a5c7b577c0a0b9a41f0401289fa55ffe22e71a7b"},{"version":"26.3.0-alpha11","sha1":"c65bf157bea9608ae6db162ae2c17027c5bac3aa"},{"version":"26.3.0-alpha12","sha1":"5ddc846469a9914449191696344c3238abbf5eef"}]}]},{"group":"com.android.tools.external.org-jetbrains","update_time":-1,"packages":[{"package":"uast","versions":[{"version":"26.0.0-alpha4","sha1":"a5f33e00d9d98972cc5f72a968fbdaf68cbd3fd"},{"version":"26.0.0-alpha5","sha1":"337117cc1ddff4bb13f5df090a131e37ddf46206"},{"version":"26.0.0-alpha6","sha1":"f67b4556d313f22e634b39b144f4b5998d5e27e1"},{"version":"26.0.0-alpha7","sha1":"d750457e24d54648bbee7ae043ad8191852e9c36"},{"version":"26.0.0-alpha8","sha1":"87dacc1d5afb822b29006d6e1c470e32de7f6995"},{"version":"26.0.0-alpha9","sha1":"160cb0a6c9b6d4906e302aa3107be8392f73721d"},{"version":"26.0.0-beta1","sha1":"a7f1e4a6389f7c4bd50824140b67e4f55f9dca5d"},{"version":"26.0.0-beta2","sha1":"3cb2632b66f166d368b87c74ebceab40caa4196e"},{"version":"26.0.0-beta3","sha1":"e6a6cfa21e9040f37d8464fc647ce5175456b0d2"},{"version":"26.0.0-beta4","sha1":"1435d3fadaf5c271b79efd901dfda7e2d805dce7"},{"version":"26.0.0-beta5","sha1":"8ffb7206ceb59693bd2549a5375258415cbeec37"},{"version":"26.0.0-beta6","sha1":"edc0e8472a76f12072fd7d0ef8109e34f451e66f"},{"version":"26.0.0-beta7","sha1":"fed16bce6fd8502013d5fc0e2d0749ce6db92bc0"},{"version":"26.0.0-rc1","sha1":"1962510c4f6f767133ae5c1174c56912cc6fce7e"},{"version":"26.0.0-rc2","sha1":"9af752e513b39f855551b793c2ff840cec85ee91"},{"version":"26.0.0","sha1":"86234f897e27e97c4aebe5686fa0e21139a1fc4e"},{"version":"26.0.1","sha1":"663906c592ce867fa2e9bcbffbe2130397078132"},{"version":"26.1.0-alpha01","sha1":"c1e060485ad1288778b1efc0833709a011025286"},{"version":"26.1.0-alpha02","sha1":"a209d82fb5adea204b513194c58a6e2661db61ca"},{"version":"26.1.0-alpha03","sha1":"f58e8e801efd0179b2f4c0580bd0fb097d720954"},{"version":"26.1.0-alpha04","sha1":"7572b94c6b30436fbed7b5cbb047a7237de9381e"},{"version":"26.1.0-alpha05","sha1":"80178046cc998af6a5e4e6187d785127df782cdc"},{"version":"26.1.0-alpha06","sha1":"4eb493a9eb8724d519821d3ef29086519c8aaf40"},{"version":"26.1.0-alpha07","sha1":"e391ff4eede0c4a15a08f0fe6f7a23e4ef9990a1"},{"version":"26.1.0-alpha08","sha1":"d60d7096ae85563fd6a7054adb97b6707ffa7806"},{"version":"26.1.0-alpha09","sha1":"d0c52f6ece1cdc47bcbaf66a0636635b826219ee"},{"version":"26.1.0-beta1","sha1":"cb2ae9f62276f407e355c4244d9cae51d027e31c"},{"version":"26.1.0-beta2","sha1":"a0e9f10c0611d009264a96376346c9ed5f977328"},{"version":"26.1.0-beta3","sha1":"69161016784a09dad3ca1a87b2966c4cd363d89a"},{"version":"26.1.0-beta4","sha1":"33638795dd13a4cb608171f63304614436c2a72b"},{"version":"26.1.0-rc01","sha1":"57719f62d9e99a5e874093617d19e211c83514fa"},{"version":"26.1.0-rc02","sha1":"de838e9609384d1287833c64956383c91f2aa3a7"},{"version":"26.1.0-rc03","sha1":"767d956e31be9605e061190afea94ec4e91dbb82"},{"version":"26.1.0","sha1":"49bdb588a091b11d4029b5bb6aa7b9466d5cd16c"},{"version":"26.1.1","sha1":"e6ed3af71d75fdc4f9d2bd18b80988f8c2800795"},{"version":"26.1.2","sha1":"615ee3b93c4f4af5d93ae7380023d4b8259513b4"},{"version":"26.1.3","sha1":"ae8226e578f866a2a31104b3bce62947fbb495b4"},{"version":"26.1.4","sha1":"fce44e4046cc2519cc50e866d6e559d1821a2d5c"},{"version":"26.2.0-alpha01","sha1":"23879a013bf3f4ddc64e3c108413da2180cd3383"},{"version":"26.2.0-alpha02","sha1":"9fdfcb5931979ce7a99503ec3f8db40c2825c66"},{"version":"26.2.0-alpha03","sha1":"d75c90c84f7fb13e20986db82d66e2cf92c3a970"},{"version":"26.2.0-alpha04","sha1":"1b508576aae51a7dbc8e448de5edbdde9472fa8f"},{"version":"26.2.0-alpha05","sha1":"7588d7fa55face4b570d7d906d98ea37cab340e1"},{"version":"26.2.0-alpha06","sha1":"fef6c071c5a164f58b11b16fa9b96a465661b6c5"},{"version":"26.2.0-alpha07","sha1":"fe59c1dd8c027f125e50305ee1c4319697924448"},{"version":"26.2.0-alpha08","sha1":"dc77f408fbb32b1fc6a94e65e35f09dfeae77485"},{"version":"26.2.0-alpha09","sha1":"dafe699c95023ae400df9079de05b9a09200167"},{"version":"26.2.0-alpha10","sha1":"6b7a3a8b7aba8026b18cea5a2c9c52001c2b93af"},{"version":"26.2.0-alpha11","sha1":"8102abb47e8117385e0d0ed8af253bf7aa2c50e2"},{"version":"26.2.0-alpha12","sha1":"7c9d28a5648bd004cdaff74cb087714bb92bf8a1"},{"version":"26.2.0-alpha13","sha1":"656cb93e6015e8433ab1e0a1656aa09b98760c7a"},{"version":"26.2.0-alpha14","sha1":"1957d1d1d5edd91f8e00de8b9eff0b0b40434438"},{"version":"26.2.0-alpha15","sha1":"a9fb1ff500c3f4d624c6a37fee80a22a75e5f988"},{"version":"26.2.0-alpha16","sha1":"86fcd371e02beaefe559382a9a1cf7af0788fded"},{"version":"26.2.0-alpha17","sha1":"9bc59c7de731e46c63579330c882f5cef3186a59"},{"version":"26.2.0-alpha18","sha1":"a3bc8ea0a2763d4d78108b64c07085e707e8408e"},{"version":"26.2.0-beta01","sha1":"ba0660d73e6f93625a9e1ea17e33e0c8de3cea5d"},{"version":"26.2.0-beta02","sha1":"8b353c60f161a8b38a9806574b739e05b7dabd4d"},{"version":"26.2.0-beta03","sha1":"9992f8748251e7f708b3cc8499f63ec3dd35d08c"},{"version":"26.2.0-beta04","sha1":"fc684a1712cb681686cd2a79d91cc0e950ecb9e5"},{"version":"26.2.0-beta05","sha1":"e6858c933594c84f78c676fb338dc0793ed442b1"},{"version":"26.2.0-rc01","sha1":"81e49015fb28c5b7328f45c1da28cd69951ef8ba"},{"version":"26.2.0-rc02","sha1":"5e443c034cb39e5c1c55a8313f9babdde293c317"},{"version":"26.2.0-rc03","sha1":"a9ac04d52754a9883b9b263caa2eae616c77ed48"},{"version":"26.2.0","sha1":"d9ee92494e437f895bc74d912fbceeff0a1c6098"},{"version":"26.2.1","sha1":"22dc9372155229b722419fff0661c01b72a2e8d0"},{"version":"26.3.0-alpha01","sha1":"a2313a13571fa191038a2cbbc9bca0606f777f"},{"version":"26.3.0-alpha02","sha1":"8ee6450162caea7d0209e9d254b806b74ff9706c"},{"version":"26.3.0-alpha03","sha1":"41e29cba45bee95063560c44f9e05215306ed0a4"},{"version":"26.3.0-alpha04","sha1":"bf0e0e30b3f8a3ce2bd54216f6222797a94dfeec"},{"version":"26.3.0-alpha05","sha1":"ca7032271e7aaf474ebe36b1782375355aaa1ebc"},{"version":"26.3.0-alpha06","sha1":"df75b50ee8bbf475bb18c214179881c49d1f4acb"},{"version":"26.3.0-alpha07","sha1":"6580227d76ddef748062c82a1ca5e38503a4c9fd"},{"version":"26.3.0-alpha08","sha1":"ecfde081e7a7b2f416c1e7ba9ae742819941c271"},{"version":"26.3.0-alpha09","sha1":"78e997252dbce2d563211a3bf58e1971e1fedcaa"},{"version":"26.3.0-alpha10","sha1":"a9ca58d5a17d1bf915a9888f08d7c909e423fdc8"},{"version":"26.3.0-alpha11","sha1":"318fc7e5a7d340e9ec4d188be6c01afd4ae67dc0"},{"version":"26.3.0-alpha12","sha1":"5a4b62e7c0c66a8a149dc6089a030d522cd4d4d1"},{"version":"26.3.0-alpha13","sha1":"e06dff1e6ce98d873e0688fbc90e5a325beb3954"},{"version":"26.3.0-beta01","sha1":"f0ffa59fb77565e45fb2cba5dc172aea92f8165a"},{"version":"26.3.0-beta02","sha1":"2f95f451322170e69d9a9d3e514ac870c8735810"},{"version":"26.3.0-beta03","sha1":"160c3fb860a7644b2f4df138da63d6beac088c13"},{"version":"26.3.0-beta04","sha1":"be4b7cbf821ce8e131593ddf35ad0085e1a3e0e0"},{"version":"26.3.0-rc01","sha1":"80c8fda36924204a3ee93aeead22ae566df3d70f"},{"version":"26.3.0-rc02","sha1":"1bd5344edee273c693c715eda86ef117e9c4cec8"},{"version":"26.3.0-rc03","sha1":"578c8ed634c5817e786fde51478dd3ad2a38224d"},{"version":"26.3.0","sha1":"2599284aedd3d2b346cdb68f5c5866005117993a"},{"version":"26.3.1","sha1":"113330d8c05c4a8576ae20d69c4fe8a82c73d6e1"},{"version":"26.3.2","sha1":"dbef8f8cb9e6b38eb921c8359605898a5f0e5ec8"},{"version":"26.4.0-alpha01","sha1":"9f1a3fd3843acead046b4e3d0aa713d0ab00976c"},{"version":"26.4.0-alpha02","sha1":"2d91cb65eee5c403113e2de21aa822d75846850b"},{"version":"26.4.0-alpha03","sha1":"cf58a8e2da59001af5db707d40435bb9ff32165a"},{"version":"26.4.0-alpha04","sha1":"ee1d6c7dbcae980bc14320c5047e8d0b85a07ff1"},{"version":"26.4.0-alpha05","sha1":"6fee3989871a8f24df7d6bffa15ac665a0a4363b"},{"version":"26.4.0-alpha06","sha1":"66da4195ed7268cd48c06b4b9c0be23b062ecfcc"},{"version":"26.4.0-alpha07","sha1":"44a45e48b006d727a817c5fc03f5a8a2753064e6"},{"version":"26.4.0-alpha08","sha1":"20fa1899efef18737f31d0172e83ef03223820a1"},{"version":"26.4.0-alpha09","sha1":"c6a3ff26d96410ec9c10e87eba61e7b032042be"},{"version":"26.4.0-alpha10","sha1":"adfb68b0aacc8074a162a1adb52b120c860d5da0"},{"version":"26.4.0-beta01","sha1":"6b5c001b634281d1679b9be61e69fc4988b2b760"},{"version":"26.4.0-beta02","sha1":"d233c02964f408d360b367e79dad3101c278574"},{"version":"26.4.0-beta03","sha1":"52528a0b273528585da958bb4ed0b60226fb2ddf"},{"version":"26.4.0-beta04","sha1":"de776a2f33f2795dbf0de8eca7323fd99b6f29c1"},{"version":"26.4.0-beta05","sha1":"a079f3bb94b6a332e19c51c7c1dd7262ee408bf5"},{"version":"26.4.0-rc01","sha1":"2fe3260a23ca3d1154abdee41873bbc0fcaee667"},{"version":"26.4.0-rc02","sha1":"b98925ce02e23d5f67829181d53640550c655381"},{"version":"26.4.0-rc03","sha1":"3280118f2fb2d577cd46a262f316d0826aeee72a"},{"version":"26.4.0","sha1":"f91f9515960cc633ae9c01d25727528e3a92c8b4"},{"version":"26.4.1","sha1":"17fa4d7c0beb607edd4943b37e191bf5c3ca1110"},{"version":"26.4.2","sha1":"2ae74785ac38ee7ffbc57c820f4944c736e4130e"},{"version":"26.5.0-alpha01","sha1":"b43af3eaed1885310c7bf744e334cf82f4722215"},{"version":"26.5.0-alpha02","sha1":"60a2a604b0de2cf39576d8373f4a76f16b7df3ef"},{"version":"26.5.0-alpha03","sha1":"26345e7c8c71fcce509a5504a63e350e49987724"},{"version":"26.5.0-alpha04","sha1":"c87a79f26a65eff741bbac8451a1bc6e05f1e45a"},{"version":"26.5.0-alpha05","sha1":"722cf05f614df7ae9267afe452674ad23dd61b98"},{"version":"26.5.0-alpha06","sha1":"7108bdfb044e3a50eefb63c5be7ed8d806e1041c"},{"version":"26.5.0-alpha07","sha1":"72c20121329ac02ea859cb9694924aa1868dbdd2"},{"version":"26.5.0-alpha08","sha1":"2bdc8896b1d578e176229973518697387dd32ede"},{"version":"26.5.0-alpha09","sha1":"cf0b3c3da9a7d148e478a7db1ba134e10c18c3c2"},{"version":"26.5.0-alpha10","sha1":"25a948a04d621bed101f2ffc1943bcaa3c485775"},{"version":"26.5.0-alpha11","sha1":"9ad2a3be1a8e9ecbc1d6b1ddffc92071aa026ac0"},{"version":"26.5.0-alpha12","sha1":"33df5a68be1c6af37b9883cb0e111788f98fa561"},{"version":"26.5.0-alpha13","sha1":"1e434ff9cbdc5d5710fa4f210034be997b3b4dd6"},{"version":"26.5.0-beta01","sha1":"1d6ba0e8d2651678f5aa2ffa59e8cfc745a4b311"},{"version":"26.5.0-beta02","sha1":"f6a22afccdc299af2281169fe52664c7c13a5794"},{"version":"26.5.0-beta03","sha1":"7ff9dddf9f7f7ebd46d2a38692d960068a04d990"},{"version":"26.5.0-beta04","sha1":"7ca0df8d610d7c0bc4f051956485fcc91c372f8f"},{"version":"26.5.0-beta05","sha1":"67a891789908f3977709683aa4970d7717e84600"},{"version":"26.6.0-alpha01","sha1":"8fb5c87eefe3ad3d3b4e3c8cf2ce26929f94c807"},{"version":"26.6.0-alpha02","sha1":"3a04765c547935d6e9cc545932df0be503612d83"},{"version":"26.6.0-alpha03","sha1":"467ab2094b84f2dce9ea1d2badc57cc98a96cec9"},{"version":"26.6.0-alpha04","sha1":"e1a75011b9770a38df94eac7a6015b9c9ba5cd0e"}]}]},{"group":"com.android.support.test.espresso.idling","update_time":-1,"packages":[{"package":"idling-net","versions":[{"version":"3.0.0","sha1":"b4eaf9fed0f24a987de649e06ad7f4ddea0ebf20"},{"version":"3.0.1-alpha-1","sha1":"b4eaf9fed0f24a987de649e06ad7f4ddea0ebf20"},{"version":"3.0.1","sha1":"b4eaf9fed0f24a987de649e06ad7f4ddea0ebf20"},{"version":"3.0.2-alpha1","sha1":"e051d1bb8f67e7495495f698be3c25d2fab6ba0e"},{"version":"3.0.2-beta1","sha1":"c8ba41c1fc099adbcbd7a4d78983ffa8a8ca2f6"},{"version":"3.0.2","sha1":"c8ba41c1fc099adbcbd7a4d78983ffa8a8ca2f6"}]},{"package":"idling-concurrent","versions":[{"version":"3.0.0","sha1":"f47d43127818b9d7fbee3fada7217bd7c26a40ef"},{"version":"3.0.1-alpha-1","sha1":"f47d43127818b9d7fbee3fada7217bd7c26a40ef"},{"version":"3.0.1","sha1":"f47d43127818b9d7fbee3fada7217bd7c26a40ef"},{"version":"3.0.2-alpha1","sha1":"c18c15fda2aeddf682b8c9f2bab6d18cf80218ba"},{"version":"3.0.2-beta1","sha1":"dd197696e371035b5a2d988656425a02b185540c"},{"version":"3.0.2","sha1":"dd197696e371035b5a2d988656425a02b185540c"}]}]},{"group":"com.android.support.test.services","update_time":-1,"packages":[{"package":"test-services","versions":[{"version":"1.0.0","sha1":"229835943d85522d049d8e5ea210c54a709b086"},{"version":"1.0.1-alpha-1","sha1":"127a20fd2ccb79c49eecafcca3c977ef5e08a920"},{"version":"1.0.1","sha1":"c24c3f3ccf05dfdd86dbcd6c7a648d6c4a429178"},{"version":"1.0.2-alpha1","sha1":"a0193b829ff91406befb8ec09cd86726857a8"},{"version":"1.0.2-beta1","sha1":"c5e5cf346a4bcca7aff00f4f0362a42bd6cad52d"},{"version":"1.0.2","sha1":"5954206f071102ca8543dae923a808b758fe2597"}]}]},{"group":"com.google.firebase","update_time":-1,"packages":[{"package":"firebase-dynamic-links","versions":[{"version":"11.0.0","sha1":"a5470a6fe1a3915651ca355463e72efb62de2faa"},{"version":"11.0.1","sha1":"3d9e402055c482a87d8d4e7cc4cd515242070750"},{"version":"11.0.2","sha1":"80d350605a7587e4ed5a6c34aac6f18cc8a962f2"},{"version":"11.0.4","sha1":"de257445671411785633c7fdca5d9b9b3df50116"},{"version":"11.2.0","sha1":"de530a1c332f90d47c182e27619bc4b4276be597"},{"version":"11.2.2","sha1":"d9049403c98af22c20387cfe166b738142cb2174"},{"version":"11.4.0","sha1":"ea30a36cfd4df7e96a72d8c5c6a57da5f2b3e08b"},{"version":"11.4.2","sha1":"cac2b9f50c993fbe16430ee2ae333f6238fbea80"},{"version":"11.6.0","sha1":"74cd05cb8045f2b9b85286aea0c08b397af957f9"},{"version":"11.6.2","sha1":"f4a53733afa1c7862a5b59a6e887104e29ee5152"},{"version":"11.8.0","sha1":"a5e6ce95bc7d1829b9e22f5f33d2990ea90b0b93"},{"version":"12.0.0","sha1":"24c283679f388b17dc2f3b8f48b8dcb800642bd1"},{"version":"12.0.1","sha1":"1ec66e89089db0cded468d658e600030c2074903"},{"version":"15.0.0","sha1":"a8005e46c3f0ea792a9254db7df2b8fcc2dcbda9"},{"version":"15.0.2","sha1":"944271900e59d47639c1774e7c64e36ef5c37377"},{"version":"16.0.0","sha1":"c7444c6fa25b4c345ed3f7253a20859b911491b3"},{"version":"16.0.1","sha1":"d8817ede53e45f6b00fbd828d2a216f333d99f22"},{"version":"16.1.1","sha1":"636b91af1bccfb43089b3bfeaf59824f7ccca134"},{"version":"16.1.2","sha1":"18eda10044661b71e038bdb4bb13889df174f0a2"},{"version":"16.1.3","sha1":"6ea1bb204a995d47ea2d8d10ea6595f7cca7ad42"},{"version":"16.1.5","sha1":"ffd8f466dc7c11b2f543dfaa4948e7b20e5e5ae8"},{"version":"16.1.7","sha1":"9ae93436d315163bf5cd4f2ac37fa60ca3ba4ccf"},{"version":"16.1.8","sha1":"58d149062d2378c6f7131d5c508ba9b25071ae6c"},{"version":"17.0.0","sha1":"61a99de783e68370ef759cc2e6cdf1fc2e8e397d"},{"version":"18.0.0","sha1":"a080ad6245d8d2b7ac92726867d7e0ad2c206932"}]},{"package":"firebase-crash","versions":[{"version":"9.0.0","sha1":"9251e3f2e663c00131e0f48a7e3d6b1d731e64e4"},{"version":"9.0.1","sha1":"e4567472bff28001d19e1b6d4e7a47ba7e1b7de4"},{"version":"9.0.2","sha1":"b5c08d7846f30708ae6526d06452b70be8504be8"},{"version":"9.2.0","sha1":"44fa37608083b0f07a8a9ba733334c3e7c9484be"},{"version":"9.2.1","sha1":"3286fbdc8dd2c348fad253c94c4c3ad1aebd6037"},{"version":"9.4.0","sha1":"d700ccdf9eb9d3d97a628e189e4e71bc177ab822"},{"version":"9.6.0","sha1":"1dfbe3494a03e3e3a22d22dfc3bb59185928cb0f"},{"version":"9.6.1","sha1":"b7168b74e103b381d25f9a58f10adc1c0d05308e"},{"version":"9.8.0","sha1":"ca5f076e928bf424a6a7997f0e62ad090eff0cf0"},{"version":"10.0.0","sha1":"ad35c1b5521d28e818ce9d83ab2c3169d2830a57"},{"version":"10.0.1","sha1":"9aeef3bd432aeb7daebe090740ab7c17a307e9e6"},{"version":"10.2.0","sha1":"674b2fd54aad2e99ccb3dbaa73755f6f2b64faf3"},{"version":"10.2.1","sha1":"4a9c1c14f1be3e319efacfb1d72cb813cdf03976"},{"version":"10.2.4","sha1":"a058a64d150758d6e453aa76258e5ba0ec59181a"},{"version":"10.2.6","sha1":"d31dc58b9152c644e47fcfaa5e943f655a05e009"},{"version":"11.0.0","sha1":"f43e3a7361b79eaf17739259e4b87deb5f8b7d0b"},{"version":"11.0.1","sha1":"d1359fc7993c8585e181fa843466c07bfa1dd8a2"},{"version":"11.0.2","sha1":"baa921b2f852734ee2ccd0fc50ec9ab490a92728"},{"version":"11.0.4","sha1":"7ca3b8187fcc5f2d8f92dd517b59d2148522da48"},{"version":"11.2.0","sha1":"c690867f0df4ddd77c1c0865b99935e2861d072b"},{"version":"11.2.2","sha1":"5ce6f6479ebf0abb672e57dc686c22b6a2ce2135"},{"version":"11.4.0","sha1":"2fc11f1fac01789ffc676d3aa99148cab1c8f0ac"},{"version":"11.4.2","sha1":"9ea26e1622027198ee2e7c090848d0e61ab35c61"},{"version":"11.6.0","sha1":"20b86efceaa089fa1cabe75cc3413b1b3e995147"},{"version":"11.6.2","sha1":"281a7139363fb298a0d8091e26de2d5556ce6e4d"},{"version":"11.8.0","sha1":"5e46458b240147c5e03b6dee9c091d9e3730c7a6"},{"version":"12.0.0","sha1":"5443a4b33ac8174e421747cccda6ef79353430b"},{"version":"12.0.1","sha1":"c5fb5903054b7a267182887409a467834780a41e"},{"version":"15.0.0","sha1":"30c6b0c2d0b4669a2e933b9ff5ff4bc9e35cdbce"},{"version":"15.0.2","sha1":"2076b9aeac67e2cb433a6527c2d989f1e3888953"},{"version":"16.0.0","sha1":"e7f0af48ff839b8bc169c430986bff4bb18459d8"},{"version":"16.0.1","sha1":"8d8ed07e8434267953cd5cbc6c465c064fb1daa4"},{"version":"16.2.0","sha1":"dddcf7ebe2b424ff945e3680e4306654eb4d6804"},{"version":"16.2.1","sha1":"24624313129a56e11daa8d993953757976d4075e"}]},{"package":"firebase-ads","versions":[{"version":"9.0.0","sha1":"6ea0753fb187a227d1f2b22fe1b27a5950c409ec"},{"version":"9.0.1","sha1":"69668d568d2e432aa7c1aeef14c5f81a003a4f38"},{"version":"9.0.2","sha1":"b7b7e7f2f24057e53a3434797abfcc67ea6e786"},{"version":"9.2.0","sha1":"116156a50d59c9af21184df385ee8f3a0ca809fa"},{"version":"9.2.1","sha1":"3e52e17a2204d383e933bb9d37e0d099dba7c8c9"},{"version":"9.4.0","sha1":"cf2b5dfa63c833baaf11e5ca854068b9987e2e52"},{"version":"9.6.0","sha1":"bb1f55395dec6e1842e08a615ff748fac60dd7a0"},{"version":"9.6.1","sha1":"ba79f8919eed3b8640e6773406fcbdb9d2a2ec3a"},{"version":"9.8.0","sha1":"44b23b5c3f61d05c46f2950e86675653054b760b"},{"version":"10.0.0","sha1":"69d0ffd3ab8e0ddc5b2a21a8d4b4bacf5f7f87e"},{"version":"10.0.1","sha1":"dc4e546682dc4b4d184045359b7647737ad2654f"},{"version":"10.2.0","sha1":"a9201f7cf64d335fb4e0cda8ac6a3c5648ba163"},{"version":"10.2.1","sha1":"d762ce00881e6d04f62e5b904dfe3f227d5c3984"},{"version":"10.2.4","sha1":"c8cb3a75ab487e99fc9f428c9c71a7c72f5e0e86"},{"version":"10.2.6","sha1":"32a4b0d3eb9fc9ddd45e5e33b40fc6642a3e3029"},{"version":"11.0.0","sha1":"9b00b5749c762ee37a38c347bb82128ba9f59fea"},{"version":"11.0.1","sha1":"3fa8b5076ee27910f21d570c99496c29b86370e3"},{"version":"11.0.2","sha1":"2ad62540a1fb79ae86da785e4902a9481593acba"},{"version":"11.0.4","sha1":"44c3692672e489ecf935ff150485b656c048beaf"},{"version":"11.2.0","sha1":"5c8836d3bf938567dad8970e4cf85574331821b9"},{"version":"11.2.2","sha1":"5d0b9eeb86a6a19e59c485668b61e1380575aa17"},{"version":"11.4.0","sha1":"52235c21ce997afbab3697b9737bdc9a11bc6380"},{"version":"11.4.2","sha1":"ed761e4b86157018320d529e1e2f7837d9efb607"},{"version":"11.6.0","sha1":"8b3bdc4704cefcd098e784c4a08cda3cc6670563"},{"version":"11.6.2","sha1":"7f8fd7a258193b5f5847e3d3fc61c498daced958"},{"version":"11.8.0","sha1":"4c48beaeb1b6333594daa54f29ce651dc9f4becc"},{"version":"12.0.0","sha1":"510cccbdc9dceaa5d6876f85e0afc16b2614b5e0"},{"version":"12.0.1","sha1":"4d07dd57f1973ae947442bb6885b74db4ef2e3f6"},{"version":"15.0.0","sha1":"fdc43150115a71d5a984fed6719c60da9f5dda00"},{"version":"15.0.1","sha1":"73069ee9f5900e944841e026c9a9d8a57205f4dd"},{"version":"16.0.1","sha1":"f311599479e9b12535b5e0b570260ed22d8aa35"},{"version":"17.0.0","sha1":"1c1b8866914502e7e1f4a105f67d9f04eb3ff881"},{"version":"17.1.0","sha1":"491781831d721285ce6abb3c8d8eb5ad8b237cad"},{"version":"17.1.1","sha1":"9ae3597b5323fbe25d18ba0e0203f33b67de414f"},{"version":"17.1.2","sha1":"fbce7a97b5b0d37ecc6887c3b13d828c000b2982"},{"version":"17.1.3","sha1":"19e14669ea82ff86dd087cff6963abb1627707f1"},{"version":"17.2.0","sha1":"27f40be7039168856e13b95f2082558eb534d412"},{"version":"17.2.1","sha1":"e9845643860686c801e83819ec889da5ff9a5815"},{"version":"18.0.0","sha1":"642e6132d745d22edf2ebe07da23dfa6cac36101"},{"version":"18.1.0","sha1":"dabe229df92bad7ab392bcd096b17f635c286872"}]},{"package":"firebase-analytics","versions":[{"version":"9.0.0","sha1":"2506f598f11059237ddbf6da5b6575ebc85c5167"},{"version":"9.0.1","sha1":"b9dac880bce72a16fdae53881c0cc3f2bf093ebf"},{"version":"9.0.2","sha1":"4e0daf6eb07a7d2109494ccd3b495d550a120287"},{"version":"9.2.0","sha1":"2ef51654e70f8a30cb3ab5b7c24bb03d95cae9fb"},{"version":"9.2.1","sha1":"94912213c74c4c118a2a05e551f895a0c9e2d199"},{"version":"9.4.0","sha1":"549bf7627fd45af74a0e2b43428cac579a6ba1c3"},{"version":"9.6.0","sha1":"5805b2c335d616288418943f5954cc5a667ea69c"},{"version":"9.6.1","sha1":"18b2bb55928651c58f55044150434d8784a6c1"},{"version":"9.8.0","sha1":"1b527e2c5d4ecab6e38a3ef6ef0a5e1ce00cd2ce"},{"version":"10.0.0","sha1":"1ed11c35245578bb8ec75cab7e37d4715340cf1e"},{"version":"10.0.1","sha1":"75c56c8f8ce322e0b7c5a2d5ba781772bba90ac7"},{"version":"10.2.0","sha1":"7612f2f7ce78c571de2faa9a2eb84d648f6058d9"},{"version":"10.2.1","sha1":"6f92b99d21c7dcd2b7d11a842ab66cf6b38e148f"},{"version":"10.2.4","sha1":"b71dc87063ece4ecbb3e77471d50878792420239"},{"version":"10.2.6","sha1":"79990fe5192b2e31da14946ddc19fcf378edbe5c"},{"version":"11.0.0","sha1":"1d9ea7f96d228a7275e535c2c9e8766307bb0b9e"},{"version":"11.0.1","sha1":"4a9226e0411cf2684f1bf3362525e66be6326358"},{"version":"11.0.2","sha1":"17ff816b9923d2db6f91a6ff35c210319b83f0cd"},{"version":"11.0.4","sha1":"f4aea4e471b8d38241e650ca438f82d17e5689e8"},{"version":"11.2.0","sha1":"29a83ace9b55c1da5f522b41b97c153b33b994f5"},{"version":"11.2.2","sha1":"ecd7c6d1c2069f5a13565a7c87aa2db8b935f8a5"},{"version":"11.4.0","sha1":"9ceb654e433e24548cd42357be6515a36e5dc54b"},{"version":"11.4.2","sha1":"1e3bb6820bfa05a674f28f3c71a4cf889f99fe83"},{"version":"11.6.0","sha1":"5ec9704c77c5f075e4fcaabfac85f0000175c731"},{"version":"11.6.2","sha1":"ed19df1da6e4802498f9462fbd5dda3f28e3d84d"},{"version":"11.8.0","sha1":"380cb6ff507bc387b44b10867dbc8b1ad7ebd687"},{"version":"12.0.0","sha1":"ffa1600af907259147caea944672aedd517d6c06"},{"version":"12.0.1","sha1":"8d0fe0af12fa8164bbf1299e3df58b19e73662f2"},{"version":"15.0.0","sha1":"de2b415d37eefa218d3b4bc23e036cf9386fc453"},{"version":"15.0.2","sha1":"96a65b75a2fdd5129ab2828d440cc2c68b7ede7d"},{"version":"16.0.0","sha1":"85c2ccdeef709fcf44fd14eda6cab851b2dda2ea"},{"version":"16.0.1","sha1":"9a72108a81cc33a20753e495adbbf8f365cf2725"},{"version":"16.0.3","sha1":"57f44ad4bd6d442e3d61db8a2636db5e4102d1b"},{"version":"16.0.4","sha1":"7e2e544d478b02c4c7611e85a8f70f1cb14ef4ec"},{"version":"16.0.5","sha1":"8213e74d141dd3da32a1e988bb91cbb31dabeaae"},{"version":"16.0.6","sha1":"74c9a7eeeb7970ddd2d15c82f9bfaa85d55cf28a"},{"version":"16.3.0","sha1":"986589b1f49584f781c9ca24eafa52cdf1ede327"},{"version":"16.4.0","sha1":"a508b906b8b09a56d08e861372aabbadd3b533e9"},{"version":"16.5.0","sha1":"72a5d90cdb5c8658deb481a1fe2c8e5dbac027f9"},{"version":"17.0.0","sha1":"e48e8f5684a1c8de58f52de4ed2d3721868b72dd"}]},{"package":"firebase-common","versions":[{"version":"9.0.0","sha1":"5170591bce6299e54b44b901e4a73f7b93bee196"},{"version":"9.0.1","sha1":"a871760fe6e7e7f5012065e2a7755fa4b7341295"},{"version":"9.0.2","sha1":"9bb7e306b82ef9cad2d0af3db82becb080f4055a"},{"version":"9.2.0","sha1":"5c39717b187b2866b859d1e4edb85cd006005ab7"},{"version":"9.2.1","sha1":"360d3c40e70e5a952237a03a66be4c9d46b0bec7"},{"version":"9.4.0","sha1":"c8a16cd024a7215bc974e1a98e1c657d4d5bec36"},{"version":"9.6.0","sha1":"6baba2738906a838614f278775fdece8ae3b6cc8"},{"version":"9.6.1","sha1":"901283d0a875b686a7f9eccabdbb41afc9465677"},{"version":"9.8.0","sha1":"c577463fc4a5348af88b54ce25491c1ca0ab8b35"},{"version":"10.0.0","sha1":"e98ecb800e4bf9ac36f4358520b780edb7f9ad0"},{"version":"10.0.1","sha1":"c924f72a53af24323b96a0d20f1d644459efb74"},{"version":"10.2.0","sha1":"13e0d6e69738b749ddc05f43ddbf5e555c86bd0b"},{"version":"10.2.1","sha1":"640927a5a75fe88a13bfbc903f5643bb52190e21"},{"version":"10.2.4","sha1":"427d823195861c16a31b48127fa0893c0cbbc27d"},{"version":"10.2.6","sha1":"7e903bbf1502dcffcc14691000f3c26009984ee"},{"version":"11.0.0","sha1":"e98ef224bb2b4586a92a0324ab1f473bd42c7d3a"},{"version":"11.0.1","sha1":"1f5fc8c20c073c202512175643de7a645ff70949"},{"version":"11.0.2","sha1":"7a156317c799aca76b3858e8d492821d4b2ce007"},{"version":"11.0.4","sha1":"581dba682b5e478e95c82f5517ae63e782555bcb"},{"version":"11.2.0","sha1":"c975ae8632805a56df228032ce551c5e5bb7e269"},{"version":"11.2.2","sha1":"2a026c29694b32fea1265b06b4f51634c28002de"},{"version":"11.4.0","sha1":"2f7ca8cf217bcd8cc9b52dc8e14d11438280362b"},{"version":"11.4.2","sha1":"b2a98be7545de28b2aaa5545a5278a3205bd05f4"},{"version":"11.6.0","sha1":"7be5970a32666f90849627fbeb1ddc6de3d071ac"},{"version":"11.6.2","sha1":"5784bcabaa0cefeb2f916cd7a6b7df03f596dfac"},{"version":"11.8.0","sha1":"f0b58295d38cfecc444ecf3f09bc2ee54f2f06da"},{"version":"12.0.0","sha1":"c470c05c28eb8a498b1a031c120e2ab7ca5a9b43"},{"version":"12.0.1","sha1":"ad949e28f0fe4bda853df26db39b34726e264a84"},{"version":"15.0.0","sha1":"fe32929660c951daa7d2edfbb617b18e755eabc0"},{"version":"15.0.1","sha1":"6f5bfca864cae504eefa698cc775bd9827162f4e"},{"version":"16.0.0","sha1":"5129ffe9c125648e2d0dd178515400f9874087b7"},{"version":"16.0.1","sha1":"6273e425ecf4f43b85695cbcf90c9d96300017f"},{"version":"16.0.2","sha1":"a2f5dea23b3c2e1c91d4e90ab643979de89de58f"},{"version":"16.0.3","sha1":"2060ba8e8e30dd53553b8f78e123cd2de9d78420"},{"version":"16.0.4","sha1":"550b350b2f231dab1d8e573d4004595c11e470ee"},{"version":"16.1.0","sha1":"6a54004276149a421245d8dd12e732a565d3314d"},{"version":"17.0.0","sha1":"204ac5f533a575e5688cf40549d5f66a3390df84"},{"version":"17.1.0","sha1":"dbffaabff70d6f5ca9f03af305cb1794f7c17a1d"},{"version":"18.0.0","sha1":"794f4a8793164e86c314510b8875542e99eafc88"}]},{"package":"firebase-auth","versions":[{"version":"9.0.0","sha1":"f2218f16a5dcc84dc6147aef7f6ed7d59b3e46b6"},{"version":"9.0.1","sha1":"f0577d6ee435495cd80095b40f7fb38103244f23"},{"version":"9.0.2","sha1":"bceb9ff87162885c0e461aae357db442879966f1"},{"version":"9.2.0","sha1":"c44ba848a7ad460cc7afb572d15c4eda7989bc7f"},{"version":"9.2.1","sha1":"cc245f8c6cdfc0e1e48b323399a4ebf39d9eb15c"},{"version":"9.4.0","sha1":"e47798b771f98c0d8578c9d783b09a133b9e7d74"},{"version":"9.6.0","sha1":"9107d37ce6b9378b3ceceb6e8b534f7b9dd0c4f9"},{"version":"9.6.1","sha1":"5e1b39efbd21b7cd1dcbe6d1fda7b329867d6513"},{"version":"9.8.0","sha1":"ffdff861af3c6aac543999b5eb82a0d0dca89cc0"},{"version":"10.0.0","sha1":"5dcd428a2b1ec675009e0f2d49858a5864c39ccb"},{"version":"10.0.1","sha1":"5cba6ac08ee78333bae7d811cc727ed809e8b7e8"},{"version":"10.2.0","sha1":"60efba5c0a535a0d2f04ecd9fb06925dacbff8e"},{"version":"10.2.1","sha1":"ef3d63ddcf2ca9a544d10689e4439f307e3c6927"},{"version":"10.2.4","sha1":"315eee9077ec9368e929eda728d2869f7f25a37c"},{"version":"10.2.6","sha1":"cd061265a15c2446925960b90988ed4e051d14cc"},{"version":"11.0.0","sha1":"cbb049a427594748d681f233dda0e37602bc630c"},{"version":"11.0.1","sha1":"de1b22e59015443b552d5a289a3cad6f063b5bef"},{"version":"11.0.2","sha1":"44ee289a57a20675cfc92fb90f7bf98f438b72c"},{"version":"11.0.4","sha1":"8e2b54cb076b97a497a30b498772527eee1c1a6d"},{"version":"11.2.0","sha1":"dd166ba4e051dd5b0ac75e8f1e51218e81550103"},{"version":"11.2.2","sha1":"f1f6d3cada3e378a8845147ae469b1c00fd1abe4"},{"version":"11.4.0","sha1":"695c2e2edbe904f61a5aafa594b4e76d94743728"},{"version":"11.4.2","sha1":"2148fdc396d34f5ca54a1dcb38248d5d7f88caaa"},{"version":"11.6.0","sha1":"c89e4f8b0ca3a7f0f3f2185703ffea0113f3e454"},{"version":"11.6.2","sha1":"fea9eb8e55a0ca9d360db8033b37065f535fc693"},{"version":"11.8.0","sha1":"9c9ec0c4f8c960973f0d9662bb91876c4ff42662"},{"version":"12.0.0","sha1":"ed1bc9ae1dc37cc5e7fe571ba1073b4572ababeb"},{"version":"12.0.1","sha1":"50a6244d4d4015f6f197440116efa656ebcc6842"},{"version":"15.0.0","sha1":"4f9f8419cff4bd0028dbdeae51e45d471af4daf5"},{"version":"15.1.0","sha1":"ea0341f3b0ec96de95e5877b6f2e61d860c92f17"},{"version":"16.0.1","sha1":"dfc2e5826a762c71fea217a703f5d1f8ee8fe3ec"},{"version":"16.0.2","sha1":"f333add1b97bbb43d3c5c71fd7c7251cfd657054"},{"version":"16.0.3","sha1":"715cd4a80c147805762712e37b71ce17727fd169"},{"version":"16.0.4","sha1":"6f90927271c866b7e053cc46b66a1b5143763132"},{"version":"16.0.5","sha1":"ec7215e868257ce773466f9433669666c5f9266e"},{"version":"16.1.0","sha1":"77e5b89baf349ed5efd081e496240654d63f0365"},{"version":"16.2.0","sha1":"60400e37615f2836dc2a2908b1ff312cfc2634b7"},{"version":"16.2.1","sha1":"f716d1064717242f97cc8f6599c189bc7e35b5fd"},{"version":"17.0.0","sha1":"c367cb1f27bdb8b12cbe24fb1ec155b8eff311c2"},{"version":"18.0.0","sha1":"d99a010d280bec16bb1aba151aa262ac6dd23cee"}]},{"package":"firebase-appindexing","versions":[{"version":"10.0.0","sha1":"4e088b308466b093cb9ca24106f45feb9ed4f704"},{"version":"10.0.1","sha1":"9bc4a98191796301a8c7fa1e4496299209ac98ca"},{"version":"10.2.0","sha1":"331f8cc995601d41be89ff31e706b6c502f190f9"},{"version":"10.2.1","sha1":"bc037a11ed43d6c54393860ec8af0b275b10ecc1"},{"version":"10.2.4","sha1":"1d5d4fd08be3832db9b7bc3a3873660c264d229b"},{"version":"10.2.6","sha1":"acbcfd0eb3468df202abd007ed6a7784a9e35c16"},{"version":"11.0.0","sha1":"ea50804c1889c82dfae92f21f1f5b3a77d1db9ad"},{"version":"11.0.1","sha1":"759672e6fec4b86a700f75f8aaf95b48b1245ffc"},{"version":"11.0.2","sha1":"589a418cc5059863501a0c441deeaa6d166d4aea"},{"version":"11.0.4","sha1":"9ee152d4af3699d6690b783dd8162dfbc41a013"},{"version":"11.2.0","sha1":"df0225f916c697bc501c605be3adbbf50da232f5"},{"version":"11.2.2","sha1":"a99d396fb07c4cc5d983db658a87bcf342f55039"},{"version":"11.4.0","sha1":"392e6356ec41504b75e5445a3cef8ab87aaa07f6"},{"version":"11.4.2","sha1":"a06346393f9ff462e52fdc763e99456b69786ca3"},{"version":"11.6.0","sha1":"7336014ed3cde8b90314b5ef67cd75b27c417bc9"},{"version":"11.6.2","sha1":"f9cf6692aded2b52583eaf88eda0d54b78bd58c4"},{"version":"11.8.0","sha1":"46890cbc687e484b22cdc79ce712a23802159edf"},{"version":"12.0.0","sha1":"d9e562503b686c7ab368ecb034a01be742d0a5bd"},{"version":"12.0.1","sha1":"f726e28e323466bcee98db0908dad46b2fa0fc83"},{"version":"15.0.0","sha1":"9f8c19f5e4cb3dd522d7cf405cc3b8ba17be6819"},{"version":"15.0.1","sha1":"b2b592d1100d83a9f76bba017c796d5015e91b22"},{"version":"16.0.1","sha1":"b99cd8305cc4052b4691908669fb5ca1d7f99cae"},{"version":"16.0.2","sha1":"e4c55bbcd14b40903f429206e48fccbdd0d2e8bb"},{"version":"17.1.0","sha1":"834665ee8bacd62667909dc7638bd3b8efd2b525"},{"version":"18.0.0","sha1":"777cb1184574db0dfceed7688cb4a54148d4314e"},{"version":"19.0.0","sha1":"2eca951addb77fe42fa75d541fdc6f87453aeb04"}]},{"package":"firebase-auth-common","versions":[{"version":"9.0.0","sha1":"4e7bd130a52bdd85aba3c82f9bbb31e8b82fa9c0"},{"version":"9.0.1","sha1":"8caa54b912f1f91a48f09f58b87063a9fc2fd877"},{"version":"9.0.2","sha1":"9bb50d1618fa82bb6d0a6e73ef1a48fd07376e74"},{"version":"9.2.0","sha1":"5acd712b0c397a610eca29beb540fea9be10d70d"},{"version":"9.2.1","sha1":"f3cf91b2bb5889837cc5e723807c698f1c2aba41"},{"version":"9.4.0","sha1":"430f35e86f56e3623791552396062499d4183988"},{"version":"9.6.0","sha1":"ab4083327060de80c44cd823e72183d09d52ccce"},{"version":"9.6.1","sha1":"eca1bb1d255de273d907470559213bc6d2bad8f3"},{"version":"9.8.0","sha1":"c0712f122fc4deb8b62c9566076e382f1af08de8"}]},{"package":"firebase-invites","versions":[{"version":"9.0.0","sha1":"7177d14cb3f93275a1fd3df4066ebc578d29b8bf"},{"version":"9.0.1","sha1":"f7ab8ce8ae93387266708cabc451ec2569f1952c"},{"version":"9.0.2","sha1":"d035f497d11906f89c47f95cf1793ddc4f611c8c"},{"version":"9.2.0","sha1":"317867986d1f17a53ba33fa96a88e5f39e7faacc"},{"version":"9.2.1","sha1":"b6cdd88863dc80db215f7cca023d9a670e3e6963"},{"version":"9.4.0","sha1":"99bf8299a24c69ea05d7d3d401d705628523dc84"},{"version":"9.6.0","sha1":"1e1e496b22f015ea3e25ba154806253b4d42221e"},{"version":"9.6.1","sha1":"5cb0de984faa34aa64306dbada18083d63f3f891"},{"version":"9.8.0","sha1":"37c52552cf7235534886dc4117cf1d9415c48b27"},{"version":"10.0.0","sha1":"68e08432c0d62b53e3ff8f79643aa42effa6af6b"},{"version":"10.0.1","sha1":"7d264c810d3100255a512c20cc84055905767d8b"},{"version":"10.2.0","sha1":"fd88cedd8c8e77c1455aca08b93f114d48e25435"},{"version":"10.2.1","sha1":"44ea25ef6a57945c5812d820a2dde911b53a1ac1"},{"version":"10.2.4","sha1":"c94f9cfade195eec1458ba5f64fbb19804c23182"},{"version":"10.2.6","sha1":"f4163e94438ad4852d713b70f8f677b8f8e2661"},{"version":"11.0.0","sha1":"30cf10a266eedb678b4f214c84fe9ce61319b835"},{"version":"11.0.1","sha1":"a486b467ebec0b6f0dc40191241256162abe1f07"},{"version":"11.0.2","sha1":"ffe2d3a84f70e29e1412de0236032e5ab1ace839"},{"version":"11.0.4","sha1":"3215e9103ec4a6b270965f50f6ed6b00895a8b92"},{"version":"11.2.0","sha1":"a486c3d3bbf9e3eb8c95d2f572fa99ef933f5f30"},{"version":"11.2.2","sha1":"8d5e0b19cb58a683921a9d7600e715e240ee69c4"},{"version":"11.4.0","sha1":"9aadc22f60fdc412d393ff57109211cc5f21a854"},{"version":"11.4.2","sha1":"33f40d1c9ccedb2ea64af6b2217971e345bd0f97"},{"version":"11.6.0","sha1":"f5daeb54465d0375a568390853d1aae01c8395ae"},{"version":"11.6.2","sha1":"c3f2bff54ec02c686e7cc3bd8c6a3c3cb9ad7e60"},{"version":"11.8.0","sha1":"9430a3eeac5fad23703ab108fb6611b561023572"},{"version":"12.0.0","sha1":"27f168c591b9ccf239b5c25828978bc9bfe9f877"},{"version":"12.0.1","sha1":"de1c7104378fcf5d122ca1c66ac72d3bc3b48eeb"},{"version":"15.0.0","sha1":"c972a03070489c335777a551d7af8666d0c7a848"},{"version":"15.0.1","sha1":"e69ed6dae2dad9b8fb1b61fae11bc02da07a0c7c"},{"version":"16.0.0","sha1":"24889e346ccb35db51cc5c7fddaed5a908618948"},{"version":"16.0.1","sha1":"6da64d5e6c4bfd068b072b169b21ad01950d68ad"},{"version":"16.0.3","sha1":"a55fc72ef8d43bf3fd1a0ce891991b2f1eb84ca3"},{"version":"16.0.4","sha1":"1d0afe4866ac36ed84976f1f9fc229d321ddd95"},{"version":"16.0.5","sha1":"747dd675df164763c742774e455ff44ec0346d35"},{"version":"16.0.6","sha1":"42643e733c48f8190ac1cc98dd91776c1b0926fa"},{"version":"16.1.0","sha1":"2e7c6054169bf18a07b80428d39aa06128885cca"},{"version":"16.1.1","sha1":"faac299e0de296a2f80d723bdc52cc27d8d71d97"},{"version":"17.0.0","sha1":"d601ba46ca1565e7c23c73f1794286b003458bda"}]},{"package":"firebase-config","versions":[{"version":"9.0.0","sha1":"b65e2904c2baa9dd4c6414643ca35505b079a59"},{"version":"9.0.1","sha1":"7f73d41885e24bd4dc358153eb130e979cabdaa2"},{"version":"9.0.2","sha1":"d61e1bc7f56813fc3c5c2f5fdf3e4890ba4a4803"},{"version":"9.2.0","sha1":"bc7d3eb2e1094b0ec843ea652c5e51d0db011c67"},{"version":"9.2.1","sha1":"d170dda657eec76bbb4442a93ee853752e42676e"},{"version":"9.4.0","sha1":"158e460226f5ceb632a98e62e7afae23073438c5"},{"version":"9.6.0","sha1":"422113253bd89dd8d7ad8d530f94c752fe9846f"},{"version":"9.6.1","sha1":"b0587704062e8dda1d82e0e4d983021da952129"},{"version":"9.8.0","sha1":"d209052e4eba0b987936950db5ccece206af8196"},{"version":"10.0.0","sha1":"3b66b872e67f3deca5dd572eea892b73942b1020"},{"version":"10.0.1","sha1":"90cd2e70453334e7221cf6d33a0ddc20f9808498"},{"version":"10.2.0","sha1":"9448a00c259e63cf813e9ad31080093b3bb867b4"},{"version":"10.2.1","sha1":"d6d053a4eb83989e4ffc6acdafa3009b8a45c812"},{"version":"10.2.4","sha1":"d29999c70aa5117a203894cd2df0bb9696ae8fed"},{"version":"10.2.6","sha1":"b1f53c84651183243cdd8305cea162fd2bbacc0b"},{"version":"11.0.0","sha1":"70cf6359a037c3063f0d044b01d6da642ae6868c"},{"version":"11.0.1","sha1":"a6e31073dcad8d72c585425a6028620dabe6d1e3"},{"version":"11.0.2","sha1":"eda15cba08bb71f50842129721cf315b8a6193a3"},{"version":"11.0.4","sha1":"32d3df68f3fbaa26cbb3c14732e8bc3639507b74"},{"version":"11.2.0","sha1":"791d83d48d4a67239981a5bf825552a625a3e918"},{"version":"11.2.2","sha1":"ac01704df770a79da6cf5b46de15251328a89014"},{"version":"11.4.0","sha1":"8275bd516db99fdff86bab020f47533ced3aad5"},{"version":"11.4.2","sha1":"4d83c992802a46804073b783149c14704d4be1b6"},{"version":"11.6.0","sha1":"a50afd8733e0f4acb7edb91429d23093540ccc77"},{"version":"11.6.2","sha1":"4396c691d8b2c0292be2cb80e0ab46b4c057c89"},{"version":"11.8.0","sha1":"6dbb868146fc536c16e78c9b0051a9f6238e482c"},{"version":"12.0.0","sha1":"aa0d47fb6002047ee247aaae5e280e5a03263d89"},{"version":"12.0.1","sha1":"efbf8453c91cba118cec6789e3a6db3b585e46aa"},{"version":"15.0.0","sha1":"3911bdb41be38bc06b4ceed1490e699cd1c983ec"},{"version":"15.0.2","sha1":"c6002fee0a27dc4c6cf9fc3988e7e599fe58e5a3"},{"version":"16.0.0","sha1":"120b3f20befd5c4e42388aae5578c3796cd49225"},{"version":"16.0.1","sha1":"2e11df1dfe52f04890a70a590451c1991856afd0"},{"version":"16.1.0","sha1":"ff3cb5e663293a3879b76ee9e40c77a32173c810"},{"version":"16.1.2","sha1":"60b7f19d09e44344f3d28fddf1c079afe1a55f12"},{"version":"16.1.3","sha1":"3079324cb4bb123ef16f5b849aeae2b2a6abef7f"},{"version":"16.3.0","sha1":"8dd57fb8db37545057713d5febc2427b8ca052de"},{"version":"16.4.0","sha1":"802ea9592efc48637cc8fa27754285fae223b4cd"},{"version":"16.4.1","sha1":"6be24658584a8ae18c2ee9d52214ae4309e2152c"},{"version":"16.5.0","sha1":"a1f1627f422dcef830898dc60697d4585a6cebd7"},{"version":"17.0.0","sha1":"e29bbd7ddb2fc8f2ac1d094d9e37006b101f7b4b"},{"version":"18.0.0","sha1":"25fcdd058236ab0ac78578579094fef0297f4ad0"}]},{"package":"firebase-analytics-impl","versions":[{"version":"9.0.0","sha1":"cfaf22f5c7523effe962af5076fe38c06d6005a9"},{"version":"9.0.1","sha1":"c763179712209face4787c9b1ebde4ffbacecd6d"},{"version":"9.0.2","sha1":"f58b2592b6aa813a5f890ba5278d78c3368e9170"},{"version":"9.2.0","sha1":"631dbafea85e26a98d8743b33042b0150cd778a6"},{"version":"9.2.1","sha1":"fffa7325e5510c8066677fac5cec5cd4ba99fe57"},{"version":"9.4.0","sha1":"c29196b1c5e8e4135645a80f91dc59b60138c57"},{"version":"9.6.0","sha1":"ae6ca2ad3b4493a5ef160c32927a5bfaf3ab7cd1"},{"version":"9.6.1","sha1":"70341a45e5b07876fb3205a6539dff480ba5e021"},{"version":"9.8.0","sha1":"8cf3a6f958e78cb392524981b258f7898af13490"},{"version":"10.0.0","sha1":"1852fce630ca2ad32e6f753fb2ee843443905835"},{"version":"10.0.1","sha1":"648973b2a114ef7d9f01927c7da9ec93faabf3e"},{"version":"10.2.0","sha1":"b3c208bd6ece26769c50a5400fb4eb07f00fdd57"},{"version":"10.2.1","sha1":"44897990e0a51c7e137de0f2c2246e6bf1b8cd23"},{"version":"10.2.4","sha1":"4f637ad492a7dd5dec1aadbb0801352a4af5a24a"},{"version":"10.2.6","sha1":"b9741dd1bf60215b90b03952a3ee197494200a63"},{"version":"11.0.0","sha1":"c49af5a32c5650894bf885bb0bdebf9bc664b8d2"},{"version":"11.0.1","sha1":"a68171a812a815987006bba22e77e003b680177"},{"version":"11.0.2","sha1":"66ce6df89dab87462ee2793b15a230d4f9d25dee"},{"version":"11.0.4","sha1":"c916a8d0ced620451df89c4dc32f94f71dc3fd01"},{"version":"11.2.0","sha1":"431326c65b60e67ef6d8a86782d0c0404e35ceec"},{"version":"11.2.2","sha1":"22face8d4fe20699a4ac9b02995064f972d785bc"},{"version":"11.4.0","sha1":"7ff4541ecbb24ed0de7033e2280b0adfe252866e"},{"version":"11.4.2","sha1":"f00bab81b5c77e0b0965ef4ee546468769bc8138"},{"version":"11.6.0","sha1":"1d24b7c9f26079ce4ea0c83b7d1c5bfb6d73bd47"},{"version":"11.6.2","sha1":"786a30d0419be14659436774ef78648cf1f4e16f"},{"version":"11.8.0","sha1":"4520186119ef027fae2acde6eff5b86c9fc47956"},{"version":"12.0.0","sha1":"e47a90b8ed3ececf4c517562aaca08f09a256a54"},{"version":"12.0.1","sha1":"64c6b39ff190d4ce23993f4a8428860affa69bf5"},{"version":"15.0.0","sha1":"41220112566a2b8c0fd61ba85c97dccd5e832de6"},{"version":"15.0.2","sha1":"5be229943ee4506b0b285b689b911637ba9cc0d5"},{"version":"16.0.0","sha1":"24af5c3916e3409af43db783728b0700a6f38891"},{"version":"16.1.1","sha1":"e3996c46341a4ff5d11a59c664b787abb68e37fa"},{"version":"16.2.1","sha1":"3bf9007622cac14d188741698bcda792c683049b"},{"version":"16.2.2","sha1":"d347da5ca82dbb215217a92185065bae3738c5c9"},{"version":"16.2.3","sha1":"813b48f49fe38be3287f0239f945b393561d9c25"},{"version":"16.2.4","sha1":"5708db9f4d9b2580094882a677b9bf74909e5dfe"},{"version":"16.3.0","sha1":"d244af79cd160284947da6139a0e07746fa4f9a3"}]},{"package":"firebase-storage","versions":[{"version":"9.0.0","sha1":"bba7c1b48c66c6836a69bb54e53ea2ad2fe5d80b"},{"version":"9.0.1","sha1":"775ca6cc4aa058d139dc141d3cbba920c13b08d7"},{"version":"9.0.2","sha1":"57a780ed2559df0b48c3e8148d3bb8bd70413377"},{"version":"9.2.0","sha1":"3e9fd23bd7504fcfe985956d1c5ce735603bb29"},{"version":"9.2.1","sha1":"f07ebff24ad2e1bc39eb11070b5175b5356f8912"},{"version":"9.4.0","sha1":"c8860fa98e548e14fee4e1f00f9b2ac3a372e0f8"},{"version":"9.6.0","sha1":"e40efa1bad02beacbfe3f790cb64f2a911eb090c"},{"version":"9.6.1","sha1":"1ebe5da6bc1b5e6605d5016b0c2e89c91d383ab1"},{"version":"9.8.0","sha1":"5de87e3fe01631999afdc40e38b692abff7d5694"},{"version":"10.0.0","sha1":"dd84d0ab83424831773822707cbadc8d97704de1"},{"version":"10.0.1","sha1":"d793098a0813a861b0a50877face115106260a39"},{"version":"10.2.0","sha1":"2f6634c4532973d03da46014185443aa188423a0"},{"version":"10.2.1","sha1":"cfa1c7d9972fe8d33223040df92a43b7242f6fcf"},{"version":"10.2.4","sha1":"99b7cc7e76836147e3483b2801a6de056fa2a8f1"},{"version":"10.2.6","sha1":"cba43d034d31ec0b2a75fba7fc62d83182f96e40"},{"version":"11.0.0","sha1":"7baae9237b8b7afa7bbfed9c0e0a9d138ca5b3be"},{"version":"11.0.1","sha1":"f3ca4b9b14b8e1a225fe109a7f828a3adb1546fa"},{"version":"11.0.2","sha1":"731c2a513765c60b3975674170c55ecd72670923"},{"version":"11.0.4","sha1":"3cbff897b18f32dac33ccb803a8809f0df1f9ce1"},{"version":"11.2.0","sha1":"e37f838d08d52bb956d36aa26327208dd3a0adc8"},{"version":"11.2.2","sha1":"310346af16eae3dc761319d5215d540d82ad5868"},{"version":"11.4.0","sha1":"318363f201fe3d51a763b3360d9864f34e57b7c1"},{"version":"11.4.2","sha1":"ee93fa279f1e58a07e2a525b8728a1214d0c0208"},{"version":"11.6.0","sha1":"d914b37a1492cc922acea7759ed78a5e67b0ef6d"},{"version":"11.6.2","sha1":"653a4d6f7672cbbc69fe66bfc80dfa8777591d7a"},{"version":"11.8.0","sha1":"f68367811b7e6a94dd94b1d081086e5c1e051f56"},{"version":"12.0.0","sha1":"3e06c2c6e35a07b39c9a6ef38e41d91d5adfdd98"},{"version":"12.0.1","sha1":"85418202a428fa954527d047e00faa96a0f0051e"},{"version":"15.0.0","sha1":"9f2171644c95e92b72916f05802972dd91f92ed8"},{"version":"15.0.2","sha1":"7afc5e9dbb3d24d3017c944ad311ae1125f988fd"},{"version":"16.0.1","sha1":"5efce82f6ad761f9987bbabec39f4044e662adb2"},{"version":"16.0.2","sha1":"2c68ad84d571af19b823798760692e75ffc36ed5"},{"version":"16.0.3","sha1":"2b469ea76c59120e5cb7deb85ec808c3f2b3195"},{"version":"16.0.4","sha1":"90685f68f31ca40395b4b6bd856f55fcaa6cfac2"},{"version":"16.0.5","sha1":"9bfb47ab53d6ce4227d0f4d81028a5c89a363912"},{"version":"16.1.0","sha1":"7698802afaaee8c7a3090b11e8dd3a54849d460d"},{"version":"17.0.0","sha1":"f8f8171a0921833dd04ff57e210736427f46730a"},{"version":"18.0.0","sha1":"f24cddce48ba0d7fc8d0c9235b4df6c139ecfc03"}]},{"package":"firebase-messaging","versions":[{"version":"9.0.0","sha1":"b94159f49460af1c469bba0f351cb3215683480a"},{"version":"9.0.1","sha1":"c85932cdd262ffcf1126d38ef13fbefeb6b93153"},{"version":"9.0.2","sha1":"95e0c3e9f39aafca0367032d134cc56770dc6b8f"},{"version":"9.2.0","sha1":"380ef64cb29c95b25453072e3c3230ce1a90b0d5"},{"version":"9.2.1","sha1":"bf9b6389a5e6691917e5df2e3d660af74def9a2a"},{"version":"9.4.0","sha1":"65cd75f56af104c6e5d786b6d161eae7ab95344b"},{"version":"9.6.0","sha1":"fe5f648fc1b5f419caa9c04f364c2382339bb3a1"},{"version":"9.6.1","sha1":"be1d8fbb5728ffea4bec7caecc1523643c31646"},{"version":"9.8.0","sha1":"927261c8867ff202a485cfdda6951c230b4de8b8"},{"version":"10.0.0","sha1":"e31f429634e9be363c1be39c2a539f9577b76396"},{"version":"10.0.1","sha1":"e4665052f96a4a99aca5bc909a5eb2416c2b2d7c"},{"version":"10.2.0","sha1":"12efc10fbb25b3e528803c26ceab1d892b6b982f"},{"version":"10.2.1","sha1":"be06ed5110b4bec5ee7650d12d83610f753fcb89"},{"version":"10.2.4","sha1":"bd0c347f1b0596b1b59f292a03b7f16e66f5eef3"},{"version":"10.2.6","sha1":"a4a781d52eac8cc01c98bbef3cc0fe81ad6ad1f1"},{"version":"11.0.0","sha1":"fba5652f73ab545d32fd4cd82eec5e649822dbe"},{"version":"11.0.1","sha1":"2d0804ecf9468f53275356a08af2a2d07e9ea116"},{"version":"11.0.2","sha1":"4cb274666ee59737149dbbe23eb63255635ec976"},{"version":"11.0.4","sha1":"1ae64296809b0b75d3fff3b9c6a97b841b5a2de5"},{"version":"11.2.0","sha1":"77196cad915871b8da4d33cc8cf582d2b6e65bde"},{"version":"11.2.2","sha1":"215b9beac254f7d3c98508a45c0aa3d7808d5175"},{"version":"11.4.0","sha1":"c479562852a8ec905cd66ff68c456e53e35cf3fa"},{"version":"11.4.2","sha1":"9ac116054d0e353fd07f5e2d4cf59069cd55aad1"},{"version":"11.6.0","sha1":"5f6971832ac88634bcaaa951886fe6063dccac86"},{"version":"11.6.2","sha1":"22c35a739fea9e75ca1e1364fe80e8a9882b646b"},{"version":"11.8.0","sha1":"438cc6c26fc8314d7a7672f0abe8eaec003b1d41"},{"version":"12.0.0","sha1":"4d82d6ca28e9e9509438a0dc4790d925556ef98c"},{"version":"12.0.1","sha1":"ab2fdfc12c79ca7dc1723822f7dacc15320b2f29"},{"version":"15.0.0","sha1":"799e14e88623d0a362f4cb9c860f1436a2a03c2c"},{"version":"15.0.2","sha1":"5e93f32ac0afebee366a52d0e4f55dd21590941c"},{"version":"17.0.0","sha1":"7daf19c4f04e302f7c4cf8a33079df77eb428e0a"},{"version":"17.1.0","sha1":"e1bd0382c948987914a4c62e42c2730d6ff1d773"},{"version":"17.3.0","sha1":"fa884948fe5fa424bdf3d875de0139d7a692f633"},{"version":"17.3.1","sha1":"3d1cf50b66c78cbc97ca788d4aca2d21e347f5f5"},{"version":"17.3.2","sha1":"87a297c897c17613422408ce1f38adc302959af6"},{"version":"17.3.3","sha1":"41d796f37179c402d9c0d09d4e5c7bfd1f7faa8f"},{"version":"17.3.4","sha1":"d64231958e6e90a466f20272651679b9fd0ab019"},{"version":"17.4.0","sha1":"9b808c09087fad16d83bec1f94ee183843acfbdb"},{"version":"17.5.0","sha1":"14c1a68bf3c4c5e1050bed6e4c8a06fa1b559e12"},{"version":"17.6.0","sha1":"64c1ad8525669c05dc4be96c73fde6f200658e19"},{"version":"18.0.0","sha1":"91d59be484cca20865c43065d674c2cd9c00b0bc"},{"version":"19.0.0","sha1":"c59317a8454a63b0edd9082343799f782c481fa1"},{"version":"19.0.1","sha1":"364ce1313b2acc04047b563a8264c261586c637d"}]},{"package":"firebase-auth-module","versions":[{"version":"9.0.0","sha1":"e8f2115f3e45d3046e0b0c3a5bcde8e2485a4840"},{"version":"9.0.1","sha1":"86012aa73f0c9216d100b8be54aaa19785720e21"},{"version":"9.0.2","sha1":"ad463ee2fa8ea8405b210f1488dae05ef4006aab"},{"version":"9.2.0","sha1":"f0429e081438845de63ccd8c9897679341c2c14a"},{"version":"9.2.1","sha1":"adcf4e2280a06d61e2493816bd5bf48ae3bd6a4e"},{"version":"9.4.0","sha1":"f140137e067707401286b85a17316f614a07328f"},{"version":"9.6.0","sha1":"fa404b55b65f8f0c57d38d4562d64ff402568fbb"},{"version":"9.6.1","sha1":"ca26a3e2e9adc6d05e7144bcc2b80b274518830"},{"version":"9.8.0","sha1":"e5cc4b88b32d8749a6807350200731ea6277cb1b"}]},{"package":"firebase-auth-impl","versions":[{"version":"11.0.0","sha1":"2b8fdfb169505276e520c7b982cef8baf4aaaa97"},{"version":"16.1.0","sha1":"c32c554bd619601afffe4f738523adec6fad8b8c"}]},{"package":"firebase-database-connection","versions":[{"version":"9.0.0","sha1":"3b763b4f92d02d6a007470a38033695819fe3db"},{"version":"9.0.1","sha1":"b754fcd7412cd00736ff2a3e964a78bcb12f8b93"},{"version":"9.0.2","sha1":"56a51d71760bf015b64063510e6f75862d8aee9e"},{"version":"9.2.0","sha1":"a92c53d47d3eab3c84b637e638695bdc08919293"},{"version":"9.2.1","sha1":"23069156bf15d4287f1c2fa7dd59cb6795e60398"},{"version":"9.4.0","sha1":"49aed79d0b30cc6f5b1a4d423310a420202cc6bd"},{"version":"9.6.0","sha1":"2f31ff82d93ece521315b8226419f026c64e2216"},{"version":"9.6.1","sha1":"730bb3d11b33dc48e0f51c1cddf4712717a50c96"},{"version":"9.8.0","sha1":"b768208c58cb8994f1617041a8b81579809b50f6"},{"version":"10.0.0","sha1":"e828d63256b73fd1238a844a62a5a25e2e3c0f31"},{"version":"10.0.1","sha1":"891dc3325eebb44254ac5084dc19295827ad8d24"},{"version":"10.2.0","sha1":"bd8bfe4e0c543386ee2b69561f2c1162d8d6a478"},{"version":"10.2.1","sha1":"b844e3db75fcc6ba5d2e599e007580458c2789d6"},{"version":"10.2.4","sha1":"5bb65f7c4a0e1cc666a2f499b17e295ba67283e"},{"version":"10.2.6","sha1":"17b2e23cdc61db3687a32e64c9b76d5f0ca986af"},{"version":"11.0.0","sha1":"b1c32d6ef9c532251ab235f295fde81c7a8e69c9"},{"version":"11.0.1","sha1":"e2024d143cee2e6c407c537bdcc92077c0cd79a7"},{"version":"11.0.2","sha1":"eaf334db899027aa9c140e1a68942aa80a6f1095"},{"version":"11.0.4","sha1":"71f57d39b43b7c33fd1689dc9d42a7f07c69ebf6"},{"version":"11.2.0","sha1":"d22e1c007b06021787b4435484656f6df310578e"},{"version":"11.2.2","sha1":"9aab4ec72e36b0e3835c8881cf358379faea2811"},{"version":"11.4.0","sha1":"6dabfaf95bdefd1ba731463750a0dedbb75b07ee"},{"version":"11.4.2","sha1":"8db1aa6193f443c80710c252d74186f549a712af"},{"version":"11.6.0","sha1":"4dc4aa96d4169d653e939641a4724ad9714ee6ac"},{"version":"11.6.2","sha1":"47d75c7a0d29e252bbff5f9987ffdb30ef1a0b63"},{"version":"11.8.0","sha1":"29dd18b04d9502c5111a2b49eafa3fe4e11dcae5"},{"version":"12.0.0","sha1":"b49067181a582a20c77c69162f400b575d3b3331"},{"version":"12.0.1","sha1":"723a109e4808cd52fa9054cc9dce975fb4194946"},{"version":"15.0.0","sha1":"68d3f37a92ccf92e209bbe01a8869375e3622aab"},{"version":"15.0.1","sha1":"2664d6e2d8d66c9581c169836ad77b68092150e0"},{"version":"16.0.1","sha1":"e1328b7cc9f9776c073b3162987dab014efe4f79"},{"version":"16.0.2","sha1":"88ebd67b6ba40852edba58df2eca27e7449c97ac"}]},{"package":"firebase-storage-common","versions":[{"version":"9.0.0","sha1":"88f3a852e947c675bdb4f4a12a62a529113bba67"},{"version":"9.0.1","sha1":"46a61c020140f77defbd18fe7298909714e1465d"},{"version":"9.0.2","sha1":"294e43a491234d536ba7a4bfff305f9fed7c40c1"},{"version":"9.2.0","sha1":"a1864a58392a35d410b07976d9a7558f6cc6c3f4"},{"version":"9.2.1","sha1":"174cf1dae8639f45386ddd9c697386b1b7da37a5"},{"version":"9.4.0","sha1":"5edc8558cf72d5921cc8fe012cdba22d55b4db16"},{"version":"9.6.0","sha1":"bdfd191c9c94ece8634be70a99fc167a8c3a9207"},{"version":"9.6.1","sha1":"23833c3e11c6f3253f37d35788c557285e54761c"},{"version":"9.8.0","sha1":"6e083010f91578cbef0b0d33965f7116d39dfcf6"},{"version":"10.0.0","sha1":"b1e5fc3a163d904cea86facb88b6bcf1d35b38b8"},{"version":"10.0.1","sha1":"2d7f74630435a0abcd8da7db733e31edca74fdd0"},{"version":"10.2.0","sha1":"eae1c00be3e42ed47d2dfd9aa8fb5b52481f909b"},{"version":"10.2.1","sha1":"2aaa20a3306ad4006c1f2706e847338d12812745"},{"version":"10.2.4","sha1":"dff016b4faab3bf3803185631254be2951f8f493"},{"version":"10.2.6","sha1":"5d72ba3cc4ed048e7193a1d106e5b5dc48cbc025"},{"version":"11.0.0","sha1":"e0e66ca1a497d499f81a9d8b8a67665c6c096bd3"},{"version":"11.0.1","sha1":"30dd5e647588b5e985afb872bc4e1931af31a61"},{"version":"11.0.2","sha1":"7e20c591babf17716917e12c034e5ec041ffb71"},{"version":"11.0.4","sha1":"19520bc1d9e0da0db70aad42f03817e5ffc916d"},{"version":"11.2.0","sha1":"a7b66b7edf609534d10b0cf3661c86ab46c5b02"},{"version":"11.2.2","sha1":"ce62e9822cbe8a2eac4105258f2b31e487907096"},{"version":"11.4.0","sha1":"b7c23eb0eefd42fa1a4d3aeb54d2f232f532c39a"},{"version":"11.4.2","sha1":"2f1afba48a99c934df37acae88408df3da3a20cc"},{"version":"11.6.0","sha1":"e5a1bfc51399e49e2075b91f01a9f7a37f0f22c8"},{"version":"11.6.2","sha1":"7ea4d166317f3145b61632b493f25bbaffda45bf"},{"version":"11.8.0","sha1":"c71bceb17b4b005bea763a87f9960416c3619bb1"},{"version":"12.0.0","sha1":"d03f3aff0257db17a084f9f5388a9bd1e47e6271"},{"version":"12.0.1","sha1":"2f2e01307aae8e0a15c796f63cc10cd6e47a574c"},{"version":"15.0.0","sha1":"a9752a04b8ad98cf77ed0af446859d5e4a1f5e30"},{"version":"15.0.2","sha1":"6e18fedb93bd2f9eec3be0d1610a8bbcd7195b29"},{"version":"16.0.1","sha1":"732bc11eff01fcd3a272850a351db405a33d7921"},{"version":"16.0.2","sha1":"2199913dfd20e3fa053b52089a014a8fb0a8c308"},{"version":"17.0.0","sha1":"48b9caff3d44fdab5347bce21d61768aa4be8da4"}]},{"package":"firebase-core","versions":[{"version":"9.0.0","sha1":"698e2b24184acb18030ab080b487f2b608ea7b79"},{"version":"9.0.1","sha1":"f74a8f207c900344a2f559515f250f2a49229765"},{"version":"9.0.2","sha1":"80397e9f390e904692a43d914399f03f1485d28d"},{"version":"9.2.0","sha1":"b381294be5dd747ed5e4e3bb376888a5e23a5481"},{"version":"9.2.1","sha1":"b54477308c6013ba68a539154663850f2705d265"},{"version":"9.4.0","sha1":"ca223d31e074917066964cda6b671e769622f4ce"},{"version":"9.6.0","sha1":"14a4a6ea63653b00d339cbdc68f62d1944c0d35f"},{"version":"9.6.1","sha1":"cfd56a95e2926076c8e47a9014f1c6fb83ea886d"},{"version":"9.8.0","sha1":"484991a0db613dac1d4914c153db778a2d85add2"},{"version":"10.0.0","sha1":"3b0fbd17b2544a9118180150dfb3f06204722002"},{"version":"10.0.1","sha1":"963660eb225c3f645e2840509a52ccbe7ef9d2e4"},{"version":"10.2.0","sha1":"d8c45e60d66c42167f103907a7bb6b1d3ab6d672"},{"version":"10.2.1","sha1":"144f78ca9cff867d40cff20ae80aebace180bd98"},{"version":"10.2.4","sha1":"4961f806c1b18072445d713b1fb7a9642edc6f37"},{"version":"10.2.6","sha1":"7c1d1e5e00b216ea8a0298f10a9905b29c455d1b"},{"version":"11.0.0","sha1":"bf70caa1ea2edcdc7b6ea20cad1d1e78e7c5b1d0"},{"version":"11.0.1","sha1":"1f012eccef93b1903cabe4c0196249444c31582b"},{"version":"11.0.2","sha1":"e633e249569933fbce69202fd42546546dfb362a"},{"version":"11.0.4","sha1":"340bd34d09117bab7ac016219dfac892b046cb2e"},{"version":"11.2.0","sha1":"5ee91873644ecea52a05f9186b4d433f8eb3493a"},{"version":"11.2.2","sha1":"3b5bb45dc1970e8428f40797b7435dc6939df55a"},{"version":"11.4.0","sha1":"9624d72bc776e7f2e106da5b914e1f656ce69dcc"},{"version":"11.4.2","sha1":"12208af7e1dcc44f2549003eddb559e3fbd7e076"},{"version":"11.6.0","sha1":"a332c2cfc32df497d527f87b74834a762c066a89"},{"version":"11.6.2","sha1":"9c490d9308b2e3e1a5ea231cf328ac88ad5d6083"},{"version":"11.8.0","sha1":"8977bb89d6b5b7a764176bd0ff66af9e1a52d67f"},{"version":"12.0.0","sha1":"5ab2ba6489f0a82f44be8d731758d89f843be60c"},{"version":"12.0.1","sha1":"8258617253b228f74385c8ea8ab420c0a9be251c"},{"version":"15.0.0","sha1":"639bbaea2f0f2eb6ca6c008dea7c5fc83ec5d055"},{"version":"15.0.2","sha1":"3a59bd7370ce7674d198c7ae508c8a6bb6d34681"},{"version":"16.0.0","sha1":"abfb8b5c6ba02ffcfb1d044dbc0fd2ae0df5c3a"},{"version":"16.0.1","sha1":"d7e40986109bb88b9d2453cda050115164fdb151"},{"version":"16.0.3","sha1":"7be52aec5e23addd4484122ea66b9ff371473f30"},{"version":"16.0.4","sha1":"38e953ce6535721ab0f444d1c024f468f0821950"},{"version":"16.0.5","sha1":"f06a30ed7a684aa68c2e8735373cabcfeb249fef"},{"version":"16.0.6","sha1":"6f2283b01a435945efadfd4b59bb74825348edd2"},{"version":"16.0.7","sha1":"c114012dad6aaa82ea2857454cc1f2410a1c2d12"},{"version":"16.0.8","sha1":"9c410c77adacec7c166fc09b08ae0d5459098b2"},{"version":"16.0.9","sha1":"bbac2d78ea36e02569eff4a69d65424804122be5"},{"version":"17.0.0","sha1":"37183ea48530044a557d95ee80e1efcfec2c7469"}]},{"package":"firebase-database","versions":[{"version":"9.0.0","sha1":"895ae061b632ac201b2a4b70506375ccf7f32c0"},{"version":"9.0.1","sha1":"fe2dd95353cc24816a6862bf7bfbaed092433233"},{"version":"9.0.2","sha1":"bfb845352d594fec64df3d4bd8323a67783814fb"},{"version":"9.2.0","sha1":"1dba58e25e0c5194a36930dd588ddb0aafef2112"},{"version":"9.2.1","sha1":"8bd38e3b26d7d8c4661419553dfaab884d4a1e57"},{"version":"9.4.0","sha1":"3901cbc7cf7baf45491878eb225a4210335a9c0"},{"version":"9.6.0","sha1":"a1b6702c978ddc50ec7ebeab2db1fa93bd36b93c"},{"version":"9.6.1","sha1":"d0bdc6f7ed1219a613e8fe70456dace53feb19a8"},{"version":"9.8.0","sha1":"fffecb5d33b3201e4e69e172836933a68827010b"},{"version":"10.0.0","sha1":"f4bbbc3fa21e9ad143ee1094c53029495ab75660"},{"version":"10.0.1","sha1":"fb8bf678151e2a54916636aaabb8ec2c575de434"},{"version":"10.2.0","sha1":"b20cf9033756bc38308cbcdd5c8eab03805e5cf6"},{"version":"10.2.1","sha1":"87933eba2a7df896dba4a78f3d41e665580f6541"},{"version":"10.2.4","sha1":"66c5c7d4139e9844f7808d70a246c0b2d7cb702"},{"version":"10.2.6","sha1":"79448761dba580c78166e95bb58f3eb0fec65d99"},{"version":"11.0.0","sha1":"d52850b1274f3b94f6c05e3df5e309caff21ba46"},{"version":"11.0.1","sha1":"3bfb08be85b1bcd135d545b761dac0ec08a8e4e5"},{"version":"11.0.2","sha1":"c262aae538522b2e29a176b683d8821ab13d0308"},{"version":"11.0.4","sha1":"1d19cd3a5fa7ba9c01f5fca0fd38a2e1e2c4f34e"},{"version":"11.2.0","sha1":"68d4e5f7dc07abbb6fcce5112ee72bc69a2fae3e"},{"version":"11.2.2","sha1":"14a16bf20ab52755af8b4bb1e2c0f284ad9e8110"},{"version":"11.4.0","sha1":"f76335255317dae45bba6ab1e2169f4af3fc248b"},{"version":"11.4.2","sha1":"3700d4cbb082bc82ed6de48067898112c8a776e6"},{"version":"11.6.0","sha1":"bd1f74b91b86782207ce7bcf21f1699173aa1854"},{"version":"11.6.2","sha1":"71da924ae7cf8f34a7445be4fc7f4bad7c6317d8"},{"version":"11.8.0","sha1":"e866b57dbc726f1be2937c4c76c9e282277966e"},{"version":"12.0.0","sha1":"ac1178ec223c83b3e776e0d47815b7faeef62c66"},{"version":"12.0.1","sha1":"31df82d3727ced22353801e219c266e0d16c197c"},{"version":"15.0.0","sha1":"ec4333de3bfd980d1960ead4fea2f6d6b9ce688a"},{"version":"15.0.1","sha1":"8c107a5ef85a38d5086d4d522868f8846226c65f"},{"version":"16.0.1","sha1":"c877fabc1918d962026ba16b5757bc161e51530d"},{"version":"16.0.2","sha1":"999f6dcf55ba753d2d9f9a85fd4da9e149aab2ed"},{"version":"16.0.3","sha1":"206080dc7c66853e7f4182cb6ad54af35a2b027"},{"version":"16.0.4","sha1":"b8da8c9d5983c3c4d87a98fc828aae740eca5d00"},{"version":"16.0.5","sha1":"e799880ae022e78cce68d57a245785b1ea1e8d58"},{"version":"16.0.6","sha1":"91fbeb73eaf19f4b34ea905741e08ebf8d738dc2"},{"version":"16.1.0","sha1":"336ebc2567dcf49511171f576f6712e27dcd969e"},{"version":"17.0.0","sha1":"f77fa6d3b042ed29c19eff1f8fe50d8c2018249c"},{"version":"18.0.0","sha1":"2b79c00ad06d1bd7e91653dc32620d0d1bc6671d"}]},{"package":"firebase-perf","versions":[{"version":"10.2.6","sha1":"d429414f90903ccc4b1459bf8cf20f36ea1729c4"},{"version":"11.0.0","sha1":"38a3c8a93a200029b15dcea2410781bf635774d8"},{"version":"11.0.1","sha1":"5075494602f2ac5345c48891bde4869b88ddf143"},{"version":"11.0.2","sha1":"2077c3cf261c19dbe96577fccbdf578042662a33"},{"version":"11.0.4","sha1":"1cce47f0ff525a4a06cc354dcef5d1ac0f300d9e"},{"version":"11.2.0","sha1":"d1dd79cf61f9a379ddeab1b5d5e2a47314868dda"},{"version":"11.2.2","sha1":"a85af19f3c606a7d2fc007517f2fabb036159a5"},{"version":"11.4.0","sha1":"d21acd4b0b5ca986afdba65b22b3b4a5a85e3173"},{"version":"11.4.2","sha1":"4a25c33c866b0a92ed8cf3f3bc918ad9b633118d"},{"version":"11.6.0","sha1":"7fef98511d08c8f1e1e8a641599e9b4e46dc4367"},{"version":"11.6.2","sha1":"9cd426abd13e4f451f719662599c19bda29e97f5"},{"version":"11.8.0","sha1":"d0b701bb19a2a863c0dae440a976181a17e469b7"},{"version":"12.0.0","sha1":"c5f782de9fd3b73f5dec5a7bd2c20db6832ea728"},{"version":"12.0.1","sha1":"a82e5e8907264a817cf6794da368e0e11d4fa340"},{"version":"15.0.0","sha1":"6e68f6e44b0c9d91756f903547ee3853349ae666"},{"version":"15.1.0","sha1":"2b00d376fee9dbb1c1d3e27dae077868eb663a53"},{"version":"15.2.0","sha1":"76eeec4bc11262cfc67f0d5ed97694ad94073ef1"},{"version":"16.0.0","sha1":"566953483c6a5ee896fc8bb415614f722010acdb"},{"version":"16.1.0","sha1":"524a96b3a6982a6f7cfbcf3d8c73d17f388f17ae"},{"version":"16.1.2","sha1":"7abfcec8f3de11d635e59d2f5200e18b61b48640"},{"version":"16.2.0","sha1":"925a57c7f62b41ff4643c0ffeef90ca6ee3e01ea"},{"version":"16.2.1","sha1":"10877b4aba4c9620aad53475fc220f1fd9cca184"},{"version":"16.2.2","sha1":"c5c6af60ac3330e41d30a430e73e219162245baa"},{"version":"16.2.3","sha1":"4983b2fbaf9e9d7019b5a72abf81161f158cc6cd"},{"version":"16.2.4","sha1":"23b305ef9791569bc8844327d9e600e1ef2bc7a"},{"version":"16.2.5","sha1":"c26129e3aa21dfcce3e62342560a46d6915b1667"},{"version":"17.0.0","sha1":"9348343e9644584561d177e4a9acffba54687daf"},{"version":"17.0.2","sha1":"b07558cca2103b98529096338e25cade92ca981b"},{"version":"18.0.0","sha1":"64ae4bd9c5c22ae1be4110c2afffacb542f2ec00"},{"version":"18.0.1","sha1":"24802f6c63b6f1f21b8ac0faf990f781cf1b3ade"}]},{"package":"firebase-iid","versions":[{"version":"9.0.0","sha1":"733466df049cdd5de415e529ba354ad14e78c23b"},{"version":"9.0.1","sha1":"7aebc8de50dfc69481fc3cf79c4ea5d2118bebe3"},{"version":"9.0.2","sha1":"3da149145120f7f29ac1fc7586796391b412f71c"},{"version":"9.2.0","sha1":"fd47b98e95afb86633d31ba349bb3e438f56e051"},{"version":"9.2.1","sha1":"c592c465c5a1fc2386c6be0244ea6e45c2d22130"},{"version":"9.4.0","sha1":"c924061e790766e89032ebfe901c4a28072f52c7"},{"version":"9.6.0","sha1":"244fe4ea2878783172668d8691f6793620659c0c"},{"version":"9.6.1","sha1":"cbe929b0a3751966b907b2a80f7363f9483fe08a"},{"version":"9.8.0","sha1":"83850c34b5b12ee8684da059e415963fa80c80a"},{"version":"10.0.0","sha1":"a96a961b9cbe4dbbe705e3a8dd5c88cb7455deaa"},{"version":"10.0.1","sha1":"734ec3c4fac8ffbb6eb5d75b738f4596574a3e4b"},{"version":"10.2.0","sha1":"9b9818842b99771c633d501c66a67bd86ef14258"},{"version":"10.2.1","sha1":"6720f465637859dbd8e94a0ed6e2188c497bc976"},{"version":"10.2.4","sha1":"194b6344c257268fa907021926e4d1304e51c2a3"},{"version":"10.2.6","sha1":"9eaa364f619827a0542206b5c3efcbb6d93cae53"},{"version":"11.0.0","sha1":"1a764a241ced4b13e5503faeb54777b8a7c327fc"},{"version":"11.0.1","sha1":"6a3f9b2871b63ed0df9cd9b3edc0586c2ea1ada1"},{"version":"11.0.2","sha1":"695251a399297ecebcb36d2e8de9ce569233b1f6"},{"version":"11.0.4","sha1":"2e2a2bf3cf156227413286f9693ced2390685878"},{"version":"11.2.0","sha1":"efabece148da9062e2020c1fa991228ca483b7b9"},{"version":"11.2.2","sha1":"9b0afee748b0829f7a817536c5fed40d33ab5a3c"},{"version":"11.4.0","sha1":"20415b0df872d4dcbec31a04f10ef6ba79e145d"},{"version":"11.4.2","sha1":"745235ae76c96e0ecf4b8521e6c47bcf71ecdb10"},{"version":"11.6.0","sha1":"bb6b9cd2403de2ed7a9684d0bbfdb7f3263a2442"},{"version":"11.6.2","sha1":"a813abd40916be9528b712c3943381bf39e41d9f"},{"version":"11.8.0","sha1":"ab4b2f46f136e13e7629db8c9bb75490a2d3e8b9"},{"version":"12.0.0","sha1":"d4ba014a11eedb3873e4a9d20db3f71b7aeb4e97"},{"version":"12.0.1","sha1":"1ba45ac9d78dd4c66224b8a7f6679b766fe13604"},{"version":"15.0.0","sha1":"b0db3336a2ffeebda40a006e15e8dc91dac5e55a"},{"version":"15.1.0","sha1":"6b6a0666fcb8e6a0c18645822e5a83952fb82bbd"},{"version":"16.0.0","sha1":"2fa7d5f783c419ce15f11fb53757a4844a9157e8"},{"version":"16.2.0","sha1":"36e75eca8eb0cd29aabbc8e2de3be64358022f3b"},{"version":"17.0.0","sha1":"e8b6e6ff73e9ee34fbba20f46604a201c2472e37"},{"version":"17.0.1","sha1":"b07b1694d34346f2d135df634f68b1b919338be1"},{"version":"17.0.2","sha1":"4949ba913ca6edf830c9c775ac8c4789e4ecb637"},{"version":"17.0.3","sha1":"228c141bcd3e51ffbb303761fc201ed1f7498753"},{"version":"17.0.4","sha1":"6a08dd1fbac746d393a5847f9a306a78d43fb3f7"},{"version":"17.1.0","sha1":"9028d34f1ee83e44bb50c9db1cbf2a49b1d29bd5"},{"version":"17.1.1","sha1":"9eb8c64ac675fdffe30d59fc4e87794acf5a09f6"},{"version":"17.1.2","sha1":"af7edf83fdb8c4fb547358dd67062ba29f9a2304"},{"version":"18.0.0","sha1":"ad263ad8a1b8bd700a72705378955a6422bf8bdc"},{"version":"19.0.0","sha1":"3026e2ef322e441223e9ea7a65bbcb4cc9853569"},{"version":"19.0.1","sha1":"82e56d3097b3905477add9e8dba5390eed902cf"}]},{"package":"firebase-iid-license","versions":[{"version":"11.4.2","sha1":"fd49e726f815b1358aa11a8107901074bf74643d"},{"version":"11.6.0","sha1":"5e0679862fd1dcd40c5bf554842da549a3dccc7c"},{"version":"11.6.2","sha1":"8a24a019bcd5728abc26e278e6a7cefe98027e5"},{"version":"11.8.0","sha1":"822d12238c5b1974d0e11d7f840df7e4bd251ccf"},{"version":"12.0.0","sha1":"7e2d043378ca0f80ba1f71391860b1487580094"},{"version":"12.0.1","sha1":"c4bdb9b55c43de304f77e288cbf7dd6e3c322076"}]},{"package":"firebase-firestore","versions":[{"version":"11.4.2","sha1":"9b763d8a3ce2776955c95cab158aeaaf6a0faf7b"},{"version":"11.6.0","sha1":"4f01edf490eb74b766c6e32311710bc54c88247"},{"version":"11.6.2","sha1":"78f2d1759748ead2695bd9be65998a0dc1335d55"},{"version":"11.8.0","sha1":"22c49715db5dc84134362bea97136301109d756c"},{"version":"12.0.0","sha1":"ff447ddf051384564fe5a7caf7ced79d5af29e4"},{"version":"12.0.1","sha1":"425f3d5c8bebe99cf2271c253d4412acf4d7c824"},{"version":"15.0.0","sha1":"59e2b5a3dffc5a65fbfa6df5907d698705b4164b"},{"version":"16.0.0","sha1":"37843000c240782ca9005cb63a3a216820070cb6"},{"version":"17.0.1","sha1":"2571e8545154cd51a92eb4766eb0713d38836855"},{"version":"17.0.2","sha1":"db3da94009593d8df8f782ddfb122e06429cb2bb"},{"version":"17.0.3","sha1":"fd0df9c1263a02fb7c8801cb1338f328b7242a4f"},{"version":"17.0.4","sha1":"c5a4592d551717871212599783b10faad4bbca57"},{"version":"17.0.5","sha1":"ac92caa150615b20c419393eba186215cbec3fdb"},{"version":"17.1.0","sha1":"ac92caa150615b20c419393eba186215cbec3fdb"},{"version":"17.1.1","sha1":"a17b068d0ff19b4bf4ecb59132ef1ccd20763766"},{"version":"17.1.2","sha1":"6f9d315c894526f49d3320d4843637360b0d8512"},{"version":"17.1.3","sha1":"3159a3108c97de0c062c0a462fba3310ec60ba78"},{"version":"17.1.4","sha1":"d8710ecf4be2279c349ca83ae2518ba375e03dcc"},{"version":"17.1.5","sha1":"fcd5a44aabaec77888b622426509a045dda76b9b"},{"version":"18.0.0","sha1":"82a38a3ade95beec75229f33a67dcc5b083b428e"},{"version":"18.0.1","sha1":"99b20b238316ec8580a7895aec7a7b1b98c75469"},{"version":"18.1.0","sha1":"73a718fb24c64ededee3676df965de0aa40d7812"},{"version":"18.2.0","sha1":"1dad7631c0dc34e359495d0e86bde6b1c985add9"},{"version":"19.0.0","sha1":"ea8df1d9b5a0d9f18cebe8e456243eda37065658"},{"version":"19.0.1","sha1":"c1ac5ecc365be885b98a8fdd58c8a54cc4f015bb"},{"version":"19.0.2","sha1":"9c81a532eecadac5ccd8164e43efb541352134bf"},{"version":"20.0.0","sha1":"89006bb840509e2ecadbd59a1fafc6dbabcdf511"},{"version":"20.1.0","sha1":"ec77be7a41ca790974138ca627940b87d3c05d45"}]},{"package":"firebase-database-license","versions":[{"version":"11.4.2","sha1":"765c744a033766f2e888aa0926c04aca530098d1"},{"version":"11.6.0","sha1":"cc0828292cb16363db2ff8f1edc2d2cd4a90779e"},{"version":"11.6.2","sha1":"151c179d5daa3e2cf503c07992a2f68aa43af7ea"},{"version":"11.8.0","sha1":"87fe62cda483a9951245a3f6ada878adf9769b30"},{"version":"12.0.0","sha1":"1ff21a62c7232550763633cb7898920124c3b83e"},{"version":"12.0.1","sha1":"e9ccb488d643c2941e283055587f32448d93eaf3"}]},{"package":"firebase-appindexing-license","versions":[{"version":"11.4.2","sha1":"6898c6c4b328213247e8f0402ef4a6b675ce61f6"},{"version":"11.6.0","sha1":"766f0f04921e2033c50bc611ec84a3f828309079"},{"version":"11.6.2","sha1":"9a05d676e5a8ed6ac5846c42bd9d72faafdabc34"},{"version":"11.8.0","sha1":"c875364f7db30608f18dfb884d28d43007f9640"},{"version":"12.0.0","sha1":"8b9c1e87b9f382e28ea937828e902b1c1c02d3c2"},{"version":"12.0.1","sha1":"2062a3cd97369af2aa460315660e97ea04872e89"}]},{"package":"firebase-analytics-impl-license","versions":[{"version":"11.4.2","sha1":"104541603941cbbaca2eb289b9b75b878384f897"},{"version":"11.6.0","sha1":"7c59291fca82f95c6200b0e0f4429ce182a1e91e"},{"version":"11.6.2","sha1":"86fbb8fefca2e61e306032b892969698a1e05237"},{"version":"11.8.0","sha1":"704dccc72601353beeff1a0074614b77030561cc"},{"version":"12.0.0","sha1":"cdf3fc4e87532a53c836ee0a027d505acfab5923"},{"version":"12.0.1","sha1":"9810fcf28700d4169d88fa94e3a3a18fa788f4da"}]},{"package":"firebase-storage-common-license","versions":[{"version":"11.4.2","sha1":"d5873c0052f0b5a518e8940be8989268218a84d9"},{"version":"11.6.0","sha1":"63f621c9d7a280f1231ec8ff75418e9f106f6d38"},{"version":"11.6.2","sha1":"401892c9970a21e09acf2fdb254c86f4ea94c16a"},{"version":"11.8.0","sha1":"a92ababe7a4e74719807a87b3161a36343d3cee6"},{"version":"12.0.0","sha1":"8c1436e6ee912036c78d6ead5b9e465ccae8bfed"},{"version":"12.0.1","sha1":"631656d73f4d208158a70a956e3e671297801d06"}]},{"package":"firebase-analytics-license","versions":[{"version":"11.4.2","sha1":"c11bbc8ace89adc7d618e98f695f7fdc307abe06"},{"version":"11.6.0","sha1":"1fb6963827a887b8b1595e2bf170e52e8aebaf20"},{"version":"11.6.2","sha1":"4d10afd4760850bc13c1e71c78f8a50813e1e45a"},{"version":"11.8.0","sha1":"ad2dff7dcbe5b0771b69edcbe930b685482768c1"},{"version":"12.0.0","sha1":"d4aa8872a7ca2ec9884b4384b4f5fbb1442a282"},{"version":"12.0.1","sha1":"5a7a01413e608ea1f2943ef0d2fbe98576d084e9"}]},{"package":"firebase-storage-license","versions":[{"version":"11.4.2","sha1":"4e34369be52513f2a38b626eb30309aa5962db6e"},{"version":"11.6.0","sha1":"13966ff8632e5783c38f02b503305ee87a6e5f73"},{"version":"11.6.2","sha1":"d8ec924067555f94f4b781a9ed17a1e9c5972e8f"},{"version":"11.8.0","sha1":"4a8fcbfac219a30929daf6ab8578fbcdcf3b441a"},{"version":"12.0.0","sha1":"2393e4373b6743f4d792a2fc399e640323f3085d"},{"version":"12.0.1","sha1":"18156bd0627e1c90469f51d71ed5fc98c79556f4"}]},{"package":"firebase-auth-license","versions":[{"version":"11.4.2","sha1":"92d529582c42c94e626f8235f65deefc572c8cd4"},{"version":"11.6.0","sha1":"51abf679be43be13356168e358380fcee4241f1f"},{"version":"11.6.2","sha1":"fe8e226d71fd0a99ea40c7b2b7d1f01cf7d95353"},{"version":"11.8.0","sha1":"8cfbc73418513f9cbe5f4c1f08e86ace6c16e100"},{"version":"12.0.0","sha1":"d5fb4c35218dc0d56531869adbd30d03f2d8f8fb"},{"version":"12.0.1","sha1":"1c8ef85649601f698c72ab1010cb929007f006b7"}]},{"package":"firebase-database-connection-license","versions":[{"version":"11.4.2","sha1":"9e70a6e845210cee03ce52995278ba4c2f0b6f88"},{"version":"11.6.0","sha1":"b93bc70789b57af20c65a15cd27050e31edf64a9"},{"version":"11.6.2","sha1":"d09ca5d404545b832d00fa957a7fe68ee29dcde9"},{"version":"11.8.0","sha1":"338e1dbecc4eba78f6784de3b0ccdada7ecc3d86"},{"version":"12.0.0","sha1":"ce2669c09e7af239f51f0e16ce375e98652a4d88"},{"version":"12.0.1","sha1":"bdfe59deb2466da3dd777955185dee24fa4e5ff"}]},{"package":"firebase-perf-license","versions":[{"version":"11.4.2","sha1":"b25343c53c38de3786d68dd9fe57b0a2372c0645"},{"version":"11.6.0","sha1":"57cd00d855b79f60eca28e1f19d7936fb811dd84"},{"version":"11.6.2","sha1":"d714a98813d5f907b88beafb676035164a423c91"},{"version":"11.8.0","sha1":"54623c07b436ec6e6b72dedb084ce3e4e56ccd52"},{"version":"12.0.0","sha1":"c54565ff07a33baf41f57c5b41dae837336a284"},{"version":"12.0.1","sha1":"246ad8820e7a974cd1fbb3cbac2929343c7dcd16"}]},{"package":"firebase-messaging-license","versions":[{"version":"11.4.2","sha1":"70eb561ce316cb051502a18ef8dedd3f43151305"},{"version":"11.6.0","sha1":"90c33f0b1c3df702c7ca431a0838ac9bda9c9621"},{"version":"11.6.2","sha1":"4d33f8607912d3a56ba722daaf3f60145980d1b1"},{"version":"11.8.0","sha1":"2a779464b0cf83c4bed44300b58322d03b1d7b13"},{"version":"12.0.0","sha1":"251062c97e9461d1f405ee4841994b57e7d39d35"},{"version":"12.0.1","sha1":"d38a1b66b4696725e054b9203d246db3d08488cf"}]},{"package":"firebase-config-license","versions":[{"version":"11.4.2","sha1":"df4b481e865ed62252760910fadbd330861a5a9a"},{"version":"11.6.0","sha1":"48d384e702ec6d580e3a5b78bc81b4530b48e240"},{"version":"11.6.2","sha1":"ef7a4e50ffdedc3c8c9ba47724f9d4ed70194491"},{"version":"11.8.0","sha1":"a138bfa7301dce78ac69facc9b80a42b74a7271c"},{"version":"12.0.0","sha1":"7d446b54b9b5f26735f2e2262530371039e13fbe"},{"version":"12.0.1","sha1":"1fba6623c8f4cc966f35c3c40220aff9500b9e59"}]},{"package":"firebase-common-license","versions":[{"version":"11.4.2","sha1":"4b7edfd059d8d8f2b29fc2f5c9b98e2c98d0488b"},{"version":"11.6.0","sha1":"12a3b2b0ca500a933f70e7f13af5cfcde3e3f4a7"},{"version":"11.6.2","sha1":"8068ec1b1faa5062c4a4d23df5ba285edaf2e1f5"},{"version":"11.8.0","sha1":"f893b6a78afb7b252ef556e8d18309918fe1103d"},{"version":"12.0.0","sha1":"68fc6e9ccea317d6b42e747902e2fe8522cf3a1c"},{"version":"12.0.1","sha1":"98d218e26dda5792df18f998416eb03c0edda8ba"}]},{"package":"firebase-dynamic-links-license","versions":[{"version":"11.4.2","sha1":"dfff9e0ec1f3f755b4ab022231257255d5a16e71"},{"version":"11.6.0","sha1":"c78e2a839ab46fbc1de56779f7aaff81025c43b6"},{"version":"11.6.2","sha1":"59eab5f422e6678e3e21c390f21309ad8d82aa9b"},{"version":"11.8.0","sha1":"cd1167a5b2019e53be82789f9032243b200cc3b4"},{"version":"12.0.0","sha1":"a5d62fc3ab2ddf1714ac1817f554f10affa54cdc"},{"version":"12.0.1","sha1":"2e3863888cda140cdb3661b6c3ea07c6a976217"}]},{"package":"firebase-crash-license","versions":[{"version":"11.6.0","sha1":"8f39d216e2bee593d32886c2db6a0930192d9d08"},{"version":"11.6.2","sha1":"13abdb37b7e086847e922c21e972d972fb7034b7"},{"version":"11.8.0","sha1":"f8e78b066c95b84f1a63633596c6ba4b7723e929"},{"version":"12.0.0","sha1":"f7d4232bb2eb064c40ef8ef9a3b8268897ed0cb3"},{"version":"12.0.1","sha1":"10cd4b8eb91a3a36160caf45191542e960c7cbd0"}]},{"package":"testlab-instr-lib","versions":[{"version":"0.1","sha1":"758eb0e4eb3276a875945f1bd3faeb469af6db19"}]},{"package":"firebase-functions-license","versions":[{"version":"12.0.0","sha1":"e6c869396601a782d697eed3533f5a3c1561cb75"},{"version":"12.0.1","sha1":"c06464e6e79be1dc154197d1b79b9bd097a61f93"}]},{"package":"firebase-functions","versions":[{"version":"12.0.0","sha1":"9e7fc542c0a8dd80327cb567e5dc10197888fe20"},{"version":"12.0.1","sha1":"9e7fc542c0a8dd80327cb567e5dc10197888fe20"},{"version":"15.0.0","sha1":"7f11d2f4a8946db0d7d7841c308fe565a39d25cc"},{"version":"16.0.1","sha1":"f1f09c48788ce39941f8c45e0891c909d1e30a45"},{"version":"16.1.0","sha1":"6002800396c8ef649b302da641f5b0d6d75ff634"},{"version":"16.1.1","sha1":"6002800396c8ef649b302da641f5b0d6d75ff634"},{"version":"16.1.2","sha1":"d52dc90119bec45db897b6498c2787cb9469ba39"},{"version":"16.1.3","sha1":"c48d5b5cce7c7db2511e4664bb916acff5c85a"},{"version":"16.2.0","sha1":"ca67a778e48a125de3f4a52966f306fc316b14ec"},{"version":"16.3.0","sha1":"5cc0a153cec78c5cb11f1b1d7fcaffa267fd4bad"},{"version":"17.0.0","sha1":"601feef62d0b5e40214dd790fe0aa28fe9fceecc"},{"version":"18.0.0","sha1":"bd69aaeecfc6ed9d99be5de80f3a0e20299ddf63"}]},{"package":"firebase-ads-lite","versions":[{"version":"15.0.0","sha1":"2964c1b1bc63ebdd2c55df57d4b540bb52546127"},{"version":"15.0.1","sha1":"a08ec8f17d88bd850d7342c157d583f04a050809"},{"version":"16.0.1","sha1":"f81fabc83c3ad0844eddc302930664c590e375e0"},{"version":"17.0.0","sha1":"7fbd1ed4d606475e025982985fa2000db8249f3"},{"version":"17.1.0","sha1":"9822a099efbc91119ef3705477f9fd29c1d5fbb0"},{"version":"17.1.1","sha1":"443884891e3dce213c568acea79f79aa3ba9d76e"},{"version":"17.1.2","sha1":"f476bef7d1b5ee7a5396fae820cd77ebddde6d21"},{"version":"17.1.3","sha1":"a6f4166b552c0fd4a4e2eb0b5d77bdbdc2b5ae44"},{"version":"17.2.0","sha1":"604deb632fa6bba99c98150e26da8a4032b0a2e3"},{"version":"17.2.1","sha1":"9f7d28ed0b4984a4860e30f0afb1760f0df04593"},{"version":"18.0.0","sha1":"ba282ecdb52cb4de72d4fcaf6e36bbb72127f171"},{"version":"18.1.0","sha1":"ae98ae1d47913b149cde8c1983243a1e76b2b5ab"}]},{"package":"firebase-database-collection","versions":[{"version":"15.0.0","sha1":"544f0de985940c679a9d6ea027e307b3e42976e1"},{"version":"15.0.1","sha1":"d64f015167a95c9f31d57a42a0424e1af99f1d71"},{"version":"16.0.0","sha1":"3ee5f3dfead596a031c9246bd654683eda7c7b89"},{"version":"16.0.1","sha1":"5ed5c71ae7bc133b067dd5ad13f7925f2f22f56"},{"version":"17.0.0","sha1":"8876d520e7bab6cd92e3ab88e95a262d68a687b7"}]},{"package":"firebase-abt","versions":[{"version":"15.0.0","sha1":"2d669322097a10bb6eab30cc770def653a925c7f"},{"version":"15.0.1","sha1":"fbdfa7124fbd8948e85ceaa569c4e4d3aa167ee7"},{"version":"16.0.0","sha1":"53bca04092bd2da5b21c69577faad478c6c6252d"},{"version":"16.0.1","sha1":"d937c42aef2e29c29f99da67ace269f9b77aa766"},{"version":"17.1.0","sha1":"2e191dc8c8547281d5aeb92db89215bd8db200d5"},{"version":"17.1.1","sha1":"32250e2ca5285ff819d7ee765785cf11c9ac4b31"},{"version":"18.0.0","sha1":"28a38623602a29522d296eaa9356058401fecda5"}]},{"package":"firebase-iid-interop","versions":[{"version":"15.0.0","sha1":"f1ae4e0b6ec52f983e16854589bd22dff7ff4dca"},{"version":"16.0.0","sha1":"b5cf5f424037602fd1ab9119127ab843223f04e7"},{"version":"16.0.1","sha1":"8707ad5e1fdd9182313a5eb5edd16e1a839cdd75"},{"version":"17.0.0","sha1":"6f329a6291b6896601c6a3c62d80e4c9f3968ba7"}]},{"package":"protolite-well-known-types","versions":[{"version":"15.0.0","sha1":"c198349dd93ae0a96ad1ed55bee347dcff49bffa"},{"version":"16.0.0","sha1":"d05d0845a12dd38e56c94d823a0abca8d299f221"},{"version":"16.0.1","sha1":"84ac1fc12bd4a483773eec3f5e0558c9813b7e27"},{"version":"17.0.0","sha1":"178953dfe96a83a1a45770a258325a3c3672feb2"}]},{"package":"firebase-measurement-connector","versions":[{"version":"15.0.0","sha1":"1bc6bb6d2016711b65dba1bfb2faf974d3a58c00"},{"version":"16.0.0","sha1":"a476b2acb932a83b21546399af838b44d46df7a3"},{"version":"17.0.0","sha1":"28052c39f08a2ed9ef1515cae9cc4fd98c703980"},{"version":"17.0.1","sha1":"bcef3a812b71fb7e3faf15f6e3eda949c7455edc"},{"version":"18.0.0","sha1":"305f4d128b2bba976f57b11d6e1859e3073b4c2d"}]},{"package":"firebase-auth-interop","versions":[{"version":"15.0.2","sha1":"ee815c307b9569fc76bae728a385f00e1b8d2027"},{"version":"16.0.0","sha1":"ce1524d68767473b43dfce07175003dec5198e98"},{"version":"16.0.1","sha1":"239d719d773917d2727677b776c1ba866cffa205"},{"version":"17.0.0","sha1":"5ce911758384328fc3936a8a02d58ba65671e770"},{"version":"18.0.0","sha1":"5fe8af802ad42d019f9829f31c7be1bf4062eaeb"}]},{"package":"firebase-measurement-connector-impl","versions":[{"version":"15.0.0","sha1":"b5009d972b6b9eeb812867e40417e584c2981610"},{"version":"16.0.0","sha1":"99666bfd0e0a4c8b24fac432f19aa033f39267ce"},{"version":"16.0.1","sha1":"80c66c7bc34d22272bbc4647414d67af0b6c84e6"},{"version":"17.0.1","sha1":"7fd6812202901504ec466c4bf764d252fbd5e8d6"},{"version":"17.0.2","sha1":"dddd9de7262df0237d64cfba4f2b6892fd430980"},{"version":"17.0.3","sha1":"78687740b33f7bafdddba5a4719bee99fbc16acb"},{"version":"17.0.4","sha1":"9ab247ec678cc409fa417cb281659ad0cb44e32b"},{"version":"17.0.5","sha1":"38325bb808eaf8455edf7f062189c59abbcbb4f0"}]},{"package":"firebase-ml-common","versions":[{"version":"15.0.0","sha1":"f68a485ed1ab64ce41893b0290693b80e0c40067"},{"version":"16.0.0","sha1":"9ee5a87d1a57add5804c8e48f22d06b0154fc204"},{"version":"16.1.2","sha1":"d43c3f97098d73dd1ed69be4159b6181de855a9b"},{"version":"16.1.4","sha1":"af1df40abf8584f5888c86906a969f99e43787de"},{"version":"16.1.5","sha1":"10d266482df9c5504cb26a022b7041ef6966cda4"},{"version":"16.1.6","sha1":"67355e93687c17e38ce0e31d709a9ff4709540e3"},{"version":"16.2.1","sha1":"5a4a710946b988c29a2d5c0b9d5605893d1e1549"},{"version":"16.2.3","sha1":"e6d1cac2a435783e5126b56c39a6ffbc96b40911"},{"version":"17.0.0","sha1":"dd9742016d701006d00944d6e2995e910b2595ab"},{"version":"19.0.0","sha1":"c53c6249043b6310a5505e2113ad46b3f18134f5"},{"version":"20.0.0","sha1":"ff0f8bb9261b7a198e049dba7c08ef043d11ebbb"}]},{"package":"firebase-ml-vision-image-label-model","versions":[{"version":"15.0.0","sha1":"77a31d229a2ee81b0e571d3f41c7e15cb8809ce9"},{"version":"16.2.0","sha1":"7fd46cfa2b4858cf771587e3393f99167f5c41f0"},{"version":"17.0.2","sha1":"45e3384e094e8888ea0e685510867b12443375c6"},{"version":"18.0.0","sha1":"bb87ac492435660de3a9ae04937e92f87b260f1d"}]},{"package":"firebase-ml-vision","versions":[{"version":"15.0.0","sha1":"8afd7cf625ebbd1883d0443ee6e0683b164154f2"},{"version":"16.0.0","sha1":"e67dcef57cfdb1f18154eae8653a0f1dee7f78f7"},{"version":"17.0.0","sha1":"6eca3dcd5182a3cba54b28e2f5e5372d7ce6cfea"},{"version":"17.0.1","sha1":"df6a671383920befb703621f18b9fb74ec1be481"},{"version":"18.0.1","sha1":"532900454f0f0dcf9b50fa55c75f1a34deb368e5"},{"version":"18.0.2","sha1":"2bafcce286c5ecc66a9fe6ff7fa85ef776b4e822"},{"version":"19.0.0","sha1":"176ecd1feb0b5356c791469bafec01b35b234cf4"},{"version":"19.0.2","sha1":"62f2b20bd6fc873aca95ea58fa6fd5b0c35e1fe4"},{"version":"19.0.3","sha1":"53dffc1401dc7d4e5e6c87a6de7ce0984d1274b1"},{"version":"20.0.0","sha1":"5e59176d2d596971019e95f71b06e42d4dd3d8f3"},{"version":"21.0.0","sha1":"c15a01002e4a64369bcf80ecf78eea89b45ff9e8"}]},{"package":"firebase-ml-model-interpreter","versions":[{"version":"15.0.0","sha1":"e4d9f53296ed9641f7d244d23a9f884d12a584b3"},{"version":"16.0.0","sha1":"6f5f05ed0f0294d03efe265f8b8a7b620fe17838"},{"version":"16.2.0","sha1":"46118c48cccd4fabeacc609c69621f19e7eef994"},{"version":"16.2.2","sha1":"7665fe8bfce0ec4a4accebbdfba6cbc9ac659b76"},{"version":"16.2.3","sha1":"334084eb80aa67786eea90520faad0500b47020d"},{"version":"16.2.4","sha1":"f639a8dabe45bb55c8794a2c7430d49eb93d3507"},{"version":"17.0.1","sha1":"3890e95c606121ce6940e2a6bfe2e35069db4fe2"},{"version":"17.0.3","sha1":"5fc0d1ab87d9f72afb8f84421f07e3620201ebdf"},{"version":"18.0.0","sha1":"ec33fbbef516bb50b1e485d1e6f86be98cd2ba9d"},{"version":"19.0.0","sha1":"f178fdf63a31b84b710418fd90369dcc3de5f03e"},{"version":"20.0.0","sha1":"e7b50fa7b7c610862d8348f33478050adc9d42f5"}]},{"package":"firebase-inappmessaging","versions":[{"version":"17.0.0","sha1":"cdc8aeffc949dc8f1d63f13b21f98c8ccbda4069"},{"version":"17.0.1","sha1":"756bb9f86db6af97c7b4f864d18d1e765339fea3"},{"version":"17.0.2","sha1":"756bb9f86db6af97c7b4f864d18d1e765339fea3"},{"version":"17.0.3","sha1":"a9842ea0dd8196cf2531d489660026f401da3f94"},{"version":"17.0.4","sha1":"703da1117319d277c328fd5a182a8b59c68e4c7a"},{"version":"17.0.5","sha1":"81ec0948fa8c6d7834fef4040d92c839ee6a5d37"},{"version":"17.1.0","sha1":"31358a325f9e41677cfe9bdd508b533738b4593e"},{"version":"17.1.1","sha1":"785bf2a2304a71b1e86faf445fc77f47ae79c759"},{"version":"17.2.0","sha1":"d9158b10d32b8ed41dcb2b2b7d24eb94e8fd1660"},{"version":"18.0.0","sha1":"945086cd9b9acd714214f552436256e51710de6d"},{"version":"18.0.1","sha1":"225370e9723f142f6bd38d6f72822c64ede96231"}]},{"package":"firebase-inappmessaging-display","versions":[{"version":"17.0.0","sha1":"32d1bf8bac69d06a668c80228e4e36ba071a4afa"},{"version":"17.0.1","sha1":"f070a6bd51eeec376ac78bdcc2c16da133b5f2b2"},{"version":"17.0.2","sha1":"f070a6bd51eeec376ac78bdcc2c16da133b5f2b2"},{"version":"17.0.3","sha1":"fae5e29e99350db09986f7b73dfd42e22ce30014"},{"version":"17.0.4","sha1":"723ca3dc2c79b824e2e3b2a5b1f02f3bee6cda5"},{"version":"17.0.5","sha1":"7ff2e0deb71ae3d22c86794c8ff51a2fa923b9b5"},{"version":"17.1.0","sha1":"938e2806d043472d0fb12dcc6b20d99aff869933"},{"version":"17.1.1","sha1":"5a8cef3299dfce4cf590782fd64ff5ce95f4b4d7"},{"version":"17.2.0","sha1":"bb6a4d592101353505d86ca1160b153d44bedd2a"},{"version":"18.0.0","sha1":"fddd9ba6f3cc992db0a24185c23de4063d36052f"},{"version":"18.0.1","sha1":"e7e6906477f8a761b3be2b12de473ed39f1bb24f"}]},{"package":"firebase-ml-vision-face-model","versions":[{"version":"17.0.2","sha1":"f9a04dd345be0b81433d0dc4672d3b80bda0a15a"},{"version":"18.0.0","sha1":"1735e91e23fa65b09c41564e24bfe0c50eef66a7"}]},{"package":"perf-plugin","versions":[{"version":"1.1.2","sha1":"dce1494a959f4c8e41b4a7627013721a466b0abf"},{"version":"1.1.3","sha1":"8c741ccaf26e8335a85763a893be882668e21255"},{"version":"1.1.4","sha1":"414dbb689e29fab905891b7a7dfa92c4d73adfee"},{"version":"1.1.5","sha1":"62390a33d9d7a2662462690da83711dce1224016"},{"version":"1.2.0","sha1":"83c2010e639b28e8de513dd062b8202765abece6"},{"version":"1.2.1","sha1":"84ad76fcb0406fc9e576299d3204bf1df911c515"},{"version":"1.3.0","sha1":"d8068504ea8896057a7fa0ed27607fed7d251a25"}]},{"package":"crash-plugin","versions":[{"version":"1.1.2","sha1":"30a3b8c2a0d1770070630a4ec09d9f68e2da5cbe"},{"version":"1.1.3","sha1":"a9f8e1ed4019c4c541e925df9f7bf06d1733d91e"},{"version":"1.1.4","sha1":"dc5a9ff6a270e810784cc51dafb3c6a3a1544295"},{"version":"1.1.5","sha1":"595651c8ab5a7028c2e0872433872b8633b954cf"}]},{"package":"firebase-plugins","versions":[{"version":"1.1.2","sha1":"7479dd4cf7c65bced31fa7214335fb733be73895"},{"version":"1.1.3","sha1":"7479dd4cf7c65bced31fa7214335fb733be73895"},{"version":"1.1.4","sha1":"7479dd4cf7c65bced31fa7214335fb733be73895"},{"version":"1.1.5","sha1":"7479dd4cf7c65bced31fa7214335fb733be73895"},{"version":"1.2.0","sha1":"7479dd4cf7c65bced31fa7214335fb733be73895"},{"version":"2.0.0","sha1":"464af4ef3933475b5fae5505fdbf9d1d2b21b4ce"}]},{"package":"firebase-ml-natural-language","versions":[{"version":"18.0.0","sha1":"f3ec8fa036fbae2a6189604a3dfe49382a2273df"},{"version":"18.1.1","sha1":"1304946cac488de1e7c8c74e3e467dd8c405cdf9"},{"version":"18.2.0","sha1":"dde04a59c464e088446bafe3ccfa1c2ff343056a"},{"version":"19.0.0","sha1":"826f4a7a63415702453ccad10aa6f57da3b020"},{"version":"19.0.1","sha1":"b0c6b7442560c6fd0cc443399d2e5675ad35b7e6"},{"version":"20.0.0","sha1":"652de963cac23f603aab1c730ad6cecd846bdaf2"}]},{"package":"firebase-ml-natural-language-language-id-model","versions":[{"version":"18.0.0","sha1":"e267958bd2aefe257433cac857f9cf455e4ae25"},{"version":"18.0.2","sha1":"8544916d8bad8c03fc5c5cbb497232f38724a3cc"},{"version":"18.0.3","sha1":"66ef0281e296950e4e51535521d7e5150db55367"},{"version":"19.0.0","sha1":"26192cee55228915164bdb7fa1a3f98e21270210"},{"version":"19.0.1","sha1":"c19dc967bd0ff12f60031888e2a8a6f2bf5764b7"},{"version":"20.0.0","sha1":"9d1687d892debcdb53aee632e316d02a00d2fefa"}]},{"package":"firebase-bom","versions":[{"version":"16.0.0","sha1":"81ba79b5f794beb8893e1ea0dd4073149e0df473"},{"version":"17.0.0","sha1":"f8f2d3119769513b91a3b3fb60c62d86339b932b"},{"version":"17.1.0","sha1":"6425101b4620b4fb4d8a65350d825142b15418cb"},{"version":"18.0.0","sha1":"8757d0f992f0bde1b6f9ff7096a0136e7053a29"},{"version":"18.1.0","sha1":"8c72656e07fc0a0e38f0be224872c8107bb47253"},{"version":"19.0.0","sha1":"6288870ef0c3a147f2adfc1ab11f98b2b080f692"},{"version":"20.0.0","sha1":"b23ef729e72ce61143dbb1ce9f4702298eca540b"},{"version":"20.0.1","sha1":"458928fafc8ef2b248deb85db0e211e476295bfe"},{"version":"20.1.0","sha1":"18509329ae2fdc0ff2482426e074672b4733860e"}]},{"package":"firebase-ml-natural-language-smart-reply-model","versions":[{"version":"18.0.0","sha1":"7ebca642b14464507d10306d82ea55ae474aa46c"},{"version":"19.0.0","sha1":"bec4eacb5f1d16ea3c08effd8e579f119ce3d7f6"},{"version":"19.0.1","sha1":"a6c319b007a6a3bebba9c0a8bf34bd6a84c2df3b"},{"version":"20.0.0","sha1":"5348ac6f98914c04cd0bfc2b5c18ac22774e89d5"}]},{"package":"firebase-ml-natural-language-smart-reply","versions":[{"version":"16.3.6","sha1":"1a3225dc33175cf9b3caaa29bd8701018177caba"},{"version":"17.0.0","sha1":"ec3fe727d91cb521445588a59a344b6f329d688f"},{"version":"17.0.1","sha1":"ff032b7d5ce24e003d6522e30c4c942edcee514f"},{"version":"18.0.0","sha1":"3c11b862498dbb0ba55b92ac63f4a6c3d6fcabdb"}]},{"package":"firebase-firestore-ktx","versions":[{"version":"18.2.0","sha1":"d129b8f6dbd9639fa50fdbbfccbf4f157c93c20a"},{"version":"19.0.0","sha1":"5ad61eecbbbd746267472771a5e5af5aad3e7b9e"},{"version":"20.0.0","sha1":"4f9bcff5f8b7f47b666705eb2910ad225689943e"},{"version":"20.1.0","sha1":"93cac08a75ba94860970ff5e2f616eb9683a0ec7"}]},{"package":"firebase-common-ktx","versions":[{"version":"16.1.0","sha1":"51b829fc0f9678193f610c40ac2799a88be40fcb"},{"version":"17.0.0","sha1":"271421851bfa05aaaf6098ebab78f83f231d824a"},{"version":"18.0.0","sha1":"230e16715f035ca8123ab08171a1b77e9288082b"}]},{"package":"firebase-ml-vision-object-detection-model","versions":[{"version":"16.0.0","sha1":"82f7dbe25b16828b41785844ecf0ee946009f0bf"},{"version":"17.0.0","sha1":"fd12a8dc6f80d13b48f54455a19bb1864604aad"}]},{"package":"firebase-ml-vision-automl","versions":[{"version":"16.0.0","sha1":"72eb942fd069362dbed94c670dc5d42f5b432707"},{"version":"17.0.0","sha1":"728ab6288117b58a127773fc98cea5697eda4670"}]},{"package":"firebase-ml-natural-language-translate","versions":[{"version":"19.0.0","sha1":"76c2ce62d42b56910330f491372293fda029be2f"},{"version":"19.0.1","sha1":"2c76b73cce1bd5f1732b6210af634ef2bf09bbbb"},{"version":"20.0.0","sha1":"72db83c9a48b28f8ee0351e329aabf54fa3d139"}]},{"package":"firebase-ml-natural-language-translate-model","versions":[{"version":"19.0.0","sha1":"a5665d389589e5076247b7cada6936f988ad3381"},{"version":"19.0.1","sha1":"ab99cc4574c61189fbf6e253eb63943c100b17c4"},{"version":"20.0.0","sha1":"a8acd98235b204eae75280feed94d415384ce6c7"}]},{"package":"firebase-datatransport","versions":[{"version":"16.0.0","sha1":"9f1a020420b7fa3e055e8f8325e639554f344e8"},{"version":"17.0.0","sha1":"c8378d9751fa325c3dbcd9496cd4fcb999c6641e"},{"version":"17.0.1","sha1":"4b6656b50a6746b6c1cb67281d10e0398ce6b515"}]}]},{"group":"com.google.android.gms","update_time":-1,"packages":[{"package":"play-services-vision","versions":[{"version":"7.8.0","sha1":"82bead3188658b3f756c541918ff3996aabb3650"},{"version":"8.1.0","sha1":"ed12b227da96f9ca18157268a767885377f95bc4"},{"version":"8.3.0","sha1":"2c02d7e8a308ec06dc80dd30c992fa0ce461962c"},{"version":"8.4.0","sha1":"afe64740129d31e548c284ee13fbf80451c62613"},{"version":"9.0.0","sha1":"67de272d4ed8050c23ba9afb536e2f2c8b4f77e9"},{"version":"9.0.1","sha1":"f7366aae24b55fe50ed9aa152ff34dd255424ef5"},{"version":"9.0.2","sha1":"1fc1ea61c049208389a0f7ca8edf864a51b93576"},{"version":"9.2.0","sha1":"4c1bd2a7775f137824bfd6f489dc9af5a5cbbb4c"},{"version":"9.2.1","sha1":"685c2ea74c309e225799cc9b692be37de0c30de8"},{"version":"9.4.0","sha1":"ab1565f92ae9ea4ff9b5eac6dc77483ceccff59d"},{"version":"9.6.0","sha1":"5f435755aca5d32bd1a6e92df66ae4209b08e5fc"},{"version":"9.6.1","sha1":"bb6f01ed1a8ce444eb739d142cbc60e52324cd64"},{"version":"9.8.0","sha1":"af4a2dbc90daf892b6da5cefd8052d67b11ced45"},{"version":"10.0.0","sha1":"d60da6b943975ba71a757b03b73260bdca175b15"},{"version":"10.0.1","sha1":"e0dfd7ebc3d27b5998249f0473fba576ed0eaaa"},{"version":"10.2.0","sha1":"e478dafec3465a28995a429fbdf98195afc454cf"},{"version":"10.2.1","sha1":"6e696aeeb860f2edeba295fd4710a84315f9899"},{"version":"10.2.4","sha1":"82e150a5f6e1c1b2bf46f0132e8b342562f88997"},{"version":"10.2.6","sha1":"21895140f31f483ddfeaf63c923676149ce7b475"},{"version":"11.0.0","sha1":"da33a1ccb5b3e94c0b97587ea6558a0b5c7881fb"},{"version":"11.0.1","sha1":"c370ce37c02b66d5dd1976c78a9f8e23edbf2873"},{"version":"11.0.2","sha1":"17f3ec919e3e0ab50259453ee62c94bdad8f04e6"},{"version":"11.0.4","sha1":"a50d6d66cafeda7520047590fb94fcf5b5ca3d42"},{"version":"11.2.0","sha1":"db52cd0cd4cb9c85b2ec9dc325c456dc6414341f"},{"version":"11.2.2","sha1":"ffc1143c3cb789c33b96721f72c32ce7f0a671fb"},{"version":"11.4.0","sha1":"c30f081f7474d6508e1935addb1bdc8366f3ed29"},{"version":"11.4.2","sha1":"fdbc17a945ce6d690f2010a8a82cf72dca69cd93"},{"version":"11.6.0","sha1":"1c421623450370a5bd8b1670a7157c05aab542f5"},{"version":"11.6.2","sha1":"f1bf2da0215355a175c5cd3063bb2df45a983d7c"},{"version":"11.8.0","sha1":"82acdcd1f493cf72990d1abb26808fc14ae1b63"},{"version":"12.0.0","sha1":"7a2fde14241e35395479ff1e794c921746b9d7c6"},{"version":"12.0.1","sha1":"79597c6723245be54c19846fc6234e1fd7cd0c55"},{"version":"15.0.0","sha1":"912150f096445afaef303b4c15a5703d5f677884"},{"version":"15.0.1","sha1":"50efa9fd4aff081ef37328357abeff6c16e3d734"},{"version":"15.0.2","sha1":"584e78444130df080cce2bda0883dce5b6598780"},{"version":"16.2.0","sha1":"662d2127dd2c1170370f27002a8d64edb91ddeb3"},{"version":"17.0.2","sha1":"9c21f12498ca761c62fcc22ac4a1efab9538fd09"},{"version":"18.0.0","sha1":"9d438a69eb34f4193cc72d94ae098a822d37e9dc"}]},{"package":"play-services-tagmanager","versions":[{"version":"9.0.0","sha1":"a9ebb7508400ce76c45c8de79299fda45a41d647"},{"version":"9.0.1","sha1":"5e07be82d6e4889520844131e566d3c3ac1457f9"},{"version":"9.0.2","sha1":"d6e782a435ee3b27017d2d43c6e3954da13a7933"},{"version":"9.2.0","sha1":"eec3cb8625e01d6f0491e2ddc28437f450f47b8d"},{"version":"9.2.1","sha1":"e6466915d59d557a54de067d6ceb64ad0b58aa9c"},{"version":"9.4.0","sha1":"493dad468c31e5c5adcde09eebed8acaac95e509"},{"version":"9.6.0","sha1":"6c64aaa83bfe5c909418d72732dfb4cf6cd5996e"},{"version":"9.6.1","sha1":"f0680d8e3c1b06657eeb7f983e6cfea33a095b56"},{"version":"9.8.0","sha1":"4120e2dc159c670d5f33e1aaf7dbda172203e76a"},{"version":"10.0.0","sha1":"9f23882c040b22f597ddfcb80393a2c13b42204b"},{"version":"10.0.1","sha1":"8f88990f1c7ffe449db1a5886de522338104f4f9"},{"version":"10.2.0","sha1":"8e45c4432504148dc39031a810c00560aa0ee48e"},{"version":"10.2.1","sha1":"91b0d86921892080660f0551298c54009ebcc764"},{"version":"10.2.4","sha1":"141f7a8528cfe75b24b17367dc11d27ca094ac75"},{"version":"10.2.6","sha1":"dac4c787e11cdcf9b177ec0341b5dfaf944087e4"},{"version":"11.0.0","sha1":"7522f8570bf378ffff8b9779e581e64c961d6e2b"},{"version":"11.0.1","sha1":"e4601d19e4b08e57b8d9d00d7a5fba94ca003085"},{"version":"11.0.2","sha1":"4494507635b14835b3c2575760dd055b792d8b5f"},{"version":"11.0.4","sha1":"6a6fc48ee01d50a194369fa052926f64b42c8a9e"},{"version":"11.2.0","sha1":"12b02bd22a1d0105beac78490026c154dfde852f"},{"version":"11.2.2","sha1":"2a714f6d3947029dadea7c3506f0d35b4ed6160d"},{"version":"11.4.0","sha1":"be4c8f0b83c8d083f92ed191dcfe89a1e500bc1"},{"version":"11.4.2","sha1":"14af706c5e60fe65d2b878bb166dd9820e1437e"},{"version":"11.6.0","sha1":"4d320b0bdf7caa731f5590606fcb69b34cd30cd3"},{"version":"11.6.2","sha1":"8c7f4f20c0d49cb4b09ff7949f3354bed9b9c808"},{"version":"11.8.0","sha1":"aff8a6e3f745ff973bf842280cb0eccede18cc93"},{"version":"12.0.0","sha1":"ceaef17beb175e45ceb6069b7fa68676920c9664"},{"version":"12.0.1","sha1":"93bc875599833fea5ab97669c2e8212f687adbf"},{"version":"15.0.0","sha1":"427380365323f89adef39a024da4fee9d68f73a3"},{"version":"15.0.2","sha1":"accdd6331fa95316efc4424ed1bf8107e9664cb0"},{"version":"16.0.0","sha1":"30679648ad1fc03f56a062419045e59f9af6422d"},{"version":"16.0.1","sha1":"3f5564290b8d2e944879f751db992c2e6455ad0f"},{"version":"16.0.3","sha1":"33a3857b170928283dfa6742017cad18b7ff1ab2"},{"version":"16.0.4","sha1":"1e071286c45a0dfdcd7c90a70886df90526b76b"},{"version":"16.0.5","sha1":"db5385f9675857b85cf0eab392ee8471fc81cee2"},{"version":"16.0.6","sha1":"e91542f5017c053b0b2e9306d74d427067d53fea"},{"version":"16.0.7","sha1":"872eaec0dc85272cf662999ef4d2d86cdec74928"},{"version":"16.0.8","sha1":"b31c2a2a85d999b80023c7905208b4ffe073495c"},{"version":"17.0.0","sha1":"a954b3b42456ec90f992a7394d43c05f3947b06"}]},{"package":"play-services-vision-common","versions":[{"version":"11.0.0","sha1":"18bc5a5b4ecf83ec6017968c54632fcd59c5b637"},{"version":"11.0.1","sha1":"bc16a6e6c6c3894cf977fc1a44f5b67138abbf17"},{"version":"11.0.2","sha1":"a2b7052a6b1749bb8d4e03f21226b8e3d4e4cee5"},{"version":"11.0.4","sha1":"1e40068736bb59fe54e2c376793cc1b8c8193f03"},{"version":"11.2.0","sha1":"d043f612770dbfd4d3f81239d03444861305205e"},{"version":"11.2.2","sha1":"f872d1cd2618f248b56a42e6a235c5096cecd53a"},{"version":"11.4.0","sha1":"798d9f99f1838f0626f095f222ba89b9457eebd4"},{"version":"11.4.2","sha1":"4c866963c0590e43ed18e90dd80194461ba95dba"},{"version":"11.6.0","sha1":"ce682baaebce9786f69fc16f294bde29e3bdc9ca"},{"version":"11.6.2","sha1":"2424b666b90e28aab21ba509500b6f25db37e169"},{"version":"11.8.0","sha1":"b8c51329bd8c87c8158a250667120e74072ef43e"},{"version":"12.0.0","sha1":"9addc1eca24160c8883c6df87fbb78e81b0111ad"},{"version":"12.0.1","sha1":"d9ff1fc3e0a97fb49187b00ec4efa71c22f19204"},{"version":"15.0.0","sha1":"71f4f162efafcc0dddd1a6e0a2476ce883ab49b3"},{"version":"15.0.1","sha1":"69d444094d685e728b3132f57b30f5625944da26"},{"version":"15.0.2","sha1":"b59e170fa37f412f8c882c96c9bfe2efd16681f5"},{"version":"16.2.0","sha1":"9bd59af57822baf180f12bee5d59d035f30417b7"},{"version":"17.0.2","sha1":"af8adbde98349556338665902b9f9a6ea1cda060"},{"version":"18.0.0","sha1":"a3f144e0fc5b5d2ab0d2389eee9ebbd19a7eb0df"}]},{"package":"play-services-all-wear","versions":[{"version":"6.5.87","sha1":"b25a6114072dae25777fdb91a2c63e4040161eee"},{"version":"7.0.0","sha1":"772c87a1bac02c2ffcba57e5daad58cb8c9a59de"},{"version":"7.3.0","sha1":"772c87a1bac02c2ffcba57e5daad58cb8c9a59de"},{"version":"7.5.0","sha1":"772c87a1bac02c2ffcba57e5daad58cb8c9a59de"},{"version":"7.8.0","sha1":"772c87a1bac02c2ffcba57e5daad58cb8c9a59de"},{"version":"8.1.0","sha1":"772c87a1bac02c2ffcba57e5daad58cb8c9a59de"},{"version":"8.3.0","sha1":"772c87a1bac02c2ffcba57e5daad58cb8c9a59de"},{"version":"8.4.0","sha1":"772c87a1bac02c2ffcba57e5daad58cb8c9a59de"},{"version":"9.0.0","sha1":"5a297e10826ee7539c532eac7cb11880d75fd5a6"},{"version":"9.0.1","sha1":"d043f013488c5f5e6d548143d05a0435ef91deb4"},{"version":"9.0.2","sha1":"f27598cb58780c0304a98376e26043d619286989"},{"version":"9.2.0","sha1":"5aae638f5f39edd7c5d1c882bd0d31676dc643b8"},{"version":"9.2.1","sha1":"1a3a2e4f2f2f006f2ca65a3bb31818cd0a9dd434"},{"version":"9.4.0","sha1":"33698734a01790e87a56eac4462a10a490552b7b"},{"version":"9.6.0","sha1":"f3aae75f6188a8a293d472433877ea2602d281d0"},{"version":"9.6.1","sha1":"a9fb7e9656bb1c2c4e77c07ce3f9fa247b9b0fcd"},{"version":"9.8.0","sha1":"b86f94fbe5c616bab7964a8a6c55836b194ba3a0"},{"version":"10.0.0","sha1":"a68fb2a5e3d53c3bbb5055ed3c72f7af4dc9ece3"},{"version":"10.0.1","sha1":"b29c33252d5983a4055db687f790c8e6bfa94796"},{"version":"10.2.0","sha1":"9d083028f5459770362763eb948a91f43fa27a42"},{"version":"10.2.1","sha1":"62457bb622ec7b1e1f432f56f43b05a3af0db84b"},{"version":"10.2.4","sha1":"8a0ad913ae77c9e0b21d11d69af5d44e7b964b5c"},{"version":"10.2.6","sha1":"c8c533b2303a8d86901670d951aa40a4028b594c"},{"version":"11.0.0","sha1":"4588ab9e6dea7845ce32144cd8d03c4d81d4b366"},{"version":"11.0.1","sha1":"5b25b5ae8be5c77331fae2e92126af5d347cd768"},{"version":"11.0.2","sha1":"64372d97ff63ae1746c53d8520440e503e7c59ce"},{"version":"11.0.4","sha1":"d9e19160f4c8b275a35fe8b26ea4f02547de11ce"},{"version":"11.2.0","sha1":"55b499786f2517ed8979db471ae4be6b46c26119"},{"version":"11.2.2","sha1":"61e8bcb41b60f2699d25e27df4e1088373357006"},{"version":"11.4.0","sha1":"6586df8a610b5f9829e44fcd2a6419e6196642ae"},{"version":"11.4.2","sha1":"4fd894a146db35ff54c75bfa84860e5db5af2e21"},{"version":"11.6.0","sha1":"e579aa48e07686ea5d4a290743ce932f52da2690"},{"version":"11.6.2","sha1":"31b264ee176f2b7528045f859e7d6a4f6b531ba8"},{"version":"11.8.0","sha1":"54ed3352f9a476e9f635ae3ddf6d64f3b18fe6a7"},{"version":"12.0.0","sha1":"1b7498f11a3a948179fde598da6b0a508c72e446"},{"version":"12.0.1","sha1":"7401cf51b678b25e15c1d59d49b8bf767646e427"},{"version":"15.0.0","sha1":"b44d94243aaa24b82cadc6be3fe4a441b735b90f"},{"version":"15.0.1","sha1":"2a96d1dd717ac8e1749d44d72ebb513740f964d3"}]},{"package":"play-services-instantapps","versions":[{"version":"9.6.0","sha1":"c5aed02902da0358796f55dd963db0fce55ff0c0"},{"version":"9.6.1","sha1":"861aa6bd3066d896a8e4b9e40c147a4071951e6b"},{"version":"9.8.0","sha1":"7b6efdaa5043361832119fed7fedf5642a353fa5"},{"version":"10.0.0","sha1":"2daf438f0bd45257bed060c26a7b90babd4ab97c"},{"version":"10.0.1","sha1":"94bcf395c9d27dfbf1a79a84970fa68235d3736d"},{"version":"10.2.0","sha1":"c0e4ef148a8911cda01194cd239ff2d017974f25"},{"version":"10.2.1","sha1":"9420aae57e0d51caf7453ff208c64c1627d82e9d"},{"version":"10.2.4","sha1":"5df74b516aa1f2aeeb30776e7595dfb4e3b3eec5"},{"version":"10.2.6","sha1":"6dc859b4d27cd86396efafdc321429539427b441"},{"version":"11.0.0","sha1":"d77b46d1eac80265b5d3d0fd70dbb2ff289139c4"},{"version":"11.0.1","sha1":"1f6e1d76cae69895d6f26ba6ef73f6269aa203d9"},{"version":"11.0.2","sha1":"dc3a92840868fc8de5f08bd44ff8796491e0e07b"},{"version":"11.0.4","sha1":"ad07d3a63f62dc0c31e3441ae799579460503530"},{"version":"11.2.0","sha1":"bf4ac11241c197eae17b83a8f2e30a5f8c539471"},{"version":"11.2.2","sha1":"144140a930addba323547c64eb7c4a274832508c"},{"version":"11.4.0","sha1":"2650c6d7b7179c8378f99e1a12d892bd00d94b7b"},{"version":"11.4.2","sha1":"89ce01953275b774a7573601881937863244cc8e"},{"version":"11.6.0","sha1":"5bd772ae50f0244e16e1a29f7900a3405d9d6f9c"},{"version":"11.6.2","sha1":"5849d9554c4ba7448d3485505833ff9ee63ad8f6"},{"version":"11.8.0","sha1":"888d5072efefedc3c552c778be5fca1cec27d625"},{"version":"12.0.0","sha1":"ea5092e0c87b7a1cb3f83bdb03667e9f1ec1e3"},{"version":"12.0.1","sha1":"9e45a6cdfbcc52766f62f9e89a10551fe7e9c944"},{"version":"15.0.0","sha1":"ae1971a1292e24a0555210eba2395488a6708529"},{"version":"15.0.1","sha1":"45a61f06bcc40dcccbbe10d8ad9b161824ddcd55"},{"version":"16.0.0","sha1":"dc6f85a105744d314c04decf242a80400e6b793d"},{"version":"16.0.1","sha1":"3c86884dc7a504cd7927cc370e6589103e7a49f4"},{"version":"16.1.0","sha1":"4caed30f5c6b2b087379cf060c3b4730de443300"},{"version":"16.1.2","sha1":"f456fa8b742bb21b80b1bbb3e84b4866c34fee8a"},{"version":"17.0.0","sha1":"1ddec5ab708ef751a979a19fc4c8ef3f6bed95d2"}]},{"package":"play-services-tasks","versions":[{"version":"9.0.0","sha1":"9a831cb55057787aa2a43bca4396b2a266eada03"},{"version":"9.0.1","sha1":"daeacdded72c0e520dee724748fa8696bb1f3dec"},{"version":"9.0.2","sha1":"c3d96c670a6376843312c495bcd9fe0bcaf802b0"},{"version":"9.2.0","sha1":"965c582c48704a77671bd15c93744d67be6737f4"},{"version":"9.2.1","sha1":"d8f62aa1b040ffdec83a629e367ee2853c8d3a"},{"version":"9.4.0","sha1":"3c9e8830143be16d453c678a3d6b9b2e5228cae8"},{"version":"9.6.0","sha1":"2ca3d4c10cb7cf28417d0e37b12a2d32385120bd"},{"version":"9.6.1","sha1":"11c3fa779c2ec1e05a582edb292920aa9c8ce462"},{"version":"9.8.0","sha1":"859bae6e100f3684397d4a7af8abf406d8ccb28b"},{"version":"10.0.0","sha1":"844ea2269a6c761c30b2856487bc8a9ee585cc7f"},{"version":"10.0.1","sha1":"8b9d596d84dbc27e4a4ccd5ffa9a05285ca9e146"},{"version":"10.2.0","sha1":"e03784445ec138472b517c94ab4507b7a3c76e69"},{"version":"10.2.1","sha1":"f38099f1c4544fc8014690783f596c237db7337"},{"version":"10.2.4","sha1":"e85209816ca83ad7932da20b68c05ed2877ecdf4"},{"version":"10.2.6","sha1":"dde714778adc62c1dde75cfdf734c9899e928506"},{"version":"11.0.0","sha1":"2bd7049ae015034a7ed8de7b41b95ddba12e2a9f"},{"version":"11.0.1","sha1":"f6592b4564d5d641d086901bb93ffa8767f38a06"},{"version":"11.0.2","sha1":"17998043b75dafac34a7dff74e83832b28db95b9"},{"version":"11.0.4","sha1":"89c4f367ca202ad4bb18208d887811a1963d85a0"},{"version":"11.2.0","sha1":"8ec99e1c7373a613c4ef416f405d45c33faa25b0"},{"version":"11.2.2","sha1":"46feaf1e6a232cc9a23adc58f86a995f3eb83906"},{"version":"11.4.0","sha1":"f4d9cab42fbb7ed92cf77d731d42f4891e0132fb"},{"version":"11.4.2","sha1":"ea66afb571ce00b4414a232b7a643b3814ddb0c4"},{"version":"11.6.0","sha1":"fb55743e8ec2b1d7d50bfcb8fe76900cdb56309a"},{"version":"11.6.2","sha1":"2737f195ba214a145523ab455056b63247071fcc"},{"version":"11.8.0","sha1":"b6dad60aa213db8f46fbd758dff9e301701ff157"},{"version":"12.0.0","sha1":"d7c5af499501c7288659220cdd5701feb89bdb26"},{"version":"12.0.1","sha1":"291a3f1fd0e68144540ce3fd7e53caaec5826b4b"},{"version":"15.0.0","sha1":"e6713fbf368261d0430e44e523eefebdc5ab4ffb"},{"version":"15.0.1","sha1":"b917ac217464595b6082ae2cf9e14fd1cfe31c7a"},{"version":"16.0.1","sha1":"565e6572641c926a370f1eaa1e0b963172ef8516"},{"version":"17.0.0","sha1":"18299a334d48bc5abfe3c9446c2a9e1a3b3f38e1"}]},{"package":"play-services-ads-lite","versions":[{"version":"9.0.0","sha1":"6fbcc4b632add16a0748c589d710efaaedeaaa74"},{"version":"9.0.1","sha1":"80d7f71416f864cfae4313ecaa6319dd258f03ce"},{"version":"9.0.2","sha1":"c51ea0a46cae2e0525d67c0f8e1c09e70dd61150"},{"version":"9.2.0","sha1":"228225cd65174750d6578e634d836a361b08af43"},{"version":"9.2.1","sha1":"5683a50f6bc3e518c5f3929153c8910cd79eb7c"},{"version":"9.4.0","sha1":"5633431fa8178d26aa0c5d16a387a6449ba6aa3f"},{"version":"9.6.0","sha1":"4be244061c664b54841c91b1bb72d8f7d1ab02c7"},{"version":"9.6.1","sha1":"511392b65c6ae0e0da87c5bf6802353f55b9fd8"},{"version":"9.8.0","sha1":"4d1953cf96b825fa1dd23bb71bdd1c1658c7c215"},{"version":"10.0.0","sha1":"a99b6e6d5730c44b14c5ac9976ff28d726690f06"},{"version":"10.0.1","sha1":"7a5672f585d1da5c2833dcfd8389cddf2976ca9"},{"version":"10.2.0","sha1":"eae111f4522c2e824052bc16eb6ef8889f4d54d"},{"version":"10.2.1","sha1":"1147dd242bc2766c997af35e3aaf54d3d8fb06ea"},{"version":"10.2.4","sha1":"6d39f0ff6cc971c294252713a7b0fc98bd59c925"},{"version":"10.2.6","sha1":"b8bda12b3d2b218460d9d7e6fbeb3f950b10d933"},{"version":"11.0.0","sha1":"c0efbc363fd6275cfb3a9d71396d23d2921605a6"},{"version":"11.0.1","sha1":"bf0af2794175333bdcc31c4594f97fb730840bb8"},{"version":"11.0.2","sha1":"857044efcbd4327d937ce1efc7efd2be9f4aec57"},{"version":"11.0.4","sha1":"be00a88406e914117cf52f7e4faeb73cca134ac5"},{"version":"11.2.0","sha1":"b2b36046b8a2a61a2706a280f481cdbab58c2cb6"},{"version":"11.2.2","sha1":"d9ea0ed74e45e842e297955b6b4398e86aa38553"},{"version":"11.4.0","sha1":"8f712851abfdb9ebf56d553861b4747fc2e82cbc"},{"version":"11.4.2","sha1":"4ed9074c6f789f3128a3ad09f99e31effe853d55"},{"version":"11.6.0","sha1":"52b8568885533acabde5c197c8b675fd4a9a3100"},{"version":"11.6.2","sha1":"2f9d706e8fbb0875b7ab05d53d8934143400d2ad"},{"version":"11.8.0","sha1":"906adc24d8e84091d4807b48344399ffd0aa3aa2"},{"version":"12.0.0","sha1":"ba4c3e99b1c44b087233fcf8bc57e209e2eb6b87"},{"version":"12.0.1","sha1":"998776bf43c45465d4f2ea4d5a1e90e34be6a6a3"},{"version":"15.0.0","sha1":"b42e7720d6f2cece619fd7bf6584fa1c187c8737"},{"version":"15.0.1","sha1":"96b0eb8ec635edaeb20053e0f75168b218586271"},{"version":"16.0.0","sha1":"bf134d4a68afd9519530707f6ca7b425bc71bb8c"},{"version":"17.0.0","sha1":"19364df1004dd164f74ad4e78bfc4552953f5772"},{"version":"17.1.0","sha1":"3170df33f73feb5f7816016a21a7ee14844a9753"},{"version":"17.1.1","sha1":"ecbaae8ad08ca70d7ccb4dcd92dfd455a8c53eb1"},{"version":"17.1.2","sha1":"d0e3ef4b113fc68fc09d3f6828e38414429ca13f"},{"version":"17.1.3","sha1":"fcae69c86f4530d893698fb269b49c6dc47bdd94"},{"version":"17.2.0","sha1":"7be8df41efe425c355a318c08243a9ef16973166"},{"version":"17.2.1","sha1":"fc75f399ac7829b1ace056aa98e45d0c04156f0"},{"version":"18.0.0","sha1":"7b9296c844f932cb094cbf0abc3ca2816a3d6b36"},{"version":"18.1.0","sha1":"8ffbd61a7e2ff98439b6593387a861029f4bc419"}]},{"package":"play-services-analytics-impl","versions":[{"version":"9.0.0","sha1":"bb8e7182a897ea4f61e220234deac9f27b4064a0"},{"version":"9.0.1","sha1":"d70828277d1de31f869a015d34695574972372ff"},{"version":"9.0.2","sha1":"df481acc93f7e8dd8c38a7dea637be9fac9015d8"},{"version":"9.2.0","sha1":"cbbf2a5b93331977481ec577a5e9255498ca0493"},{"version":"9.2.1","sha1":"c53f7ab1964766872cb7de950b2da9bcda8b87a"},{"version":"9.4.0","sha1":"692e21d507e3584c7529354c336970fe17c0f607"},{"version":"9.6.0","sha1":"789a2cf959ab01e9f87b1f635cc363e21021cd0c"},{"version":"9.6.1","sha1":"3f2f8f48bc0388cd37eccb35fe0e5162e575c346"},{"version":"9.8.0","sha1":"b32ab7b80f37b8d1afa93f8503b95c449936d523"},{"version":"10.0.0","sha1":"5fbb774d0080cb62d3eb04096245d29724294040"},{"version":"10.0.1","sha1":"5ee73d1908d0f61b1345bce7ac3ca9b445ab679d"},{"version":"10.2.0","sha1":"9519b75c05c89d67d2e9077c45df7a878410b95f"},{"version":"10.2.1","sha1":"1ac20627fc8aee0189af2f2f39b014a4158a98e"},{"version":"10.2.4","sha1":"d666d661d5c57171d42fe6c69847f9513391d3d3"},{"version":"10.2.6","sha1":"4c08597fed2874efd420969530a10e523ff340e5"},{"version":"11.0.0","sha1":"e10d20b0bf4636bd791947358c683f01e0e9e178"},{"version":"11.0.1","sha1":"f940102cc77756d87be4a89097b80a9f286c437c"},{"version":"11.0.2","sha1":"48826906c0a4fbdcf96b0a425393fd4dac90a700"},{"version":"11.0.4","sha1":"edcf563aaf8d7d4e827444888404200d9b1aa490"},{"version":"11.2.0","sha1":"beb68db182ac6e4634888a456c04c96240e0b860"},{"version":"11.2.2","sha1":"ce0f424233d2e119e0d7718508c10e49f24b8123"},{"version":"11.4.0","sha1":"5a44dcd8549345ec9dee8601fcf1213f097e762a"},{"version":"11.4.2","sha1":"a31f2a1046494bfaa1cfa391a96eea80597af02f"},{"version":"11.6.0","sha1":"a7daf9dfbeb6f56061e5d4fb6e7c75bc646a86b0"},{"version":"11.6.2","sha1":"c23fa64541c29f313b06ad6718ed8eafaf965e58"},{"version":"11.8.0","sha1":"25fe1dee37493aea67358cf3656b64d947e12778"},{"version":"12.0.0","sha1":"4302d7203343ea1ca3ffaa721d5d3d7caf90ed44"},{"version":"12.0.1","sha1":"f0641f4748c9693ceb0ce8c600922028ffc848b1"},{"version":"15.0.0","sha1":"b162f59f92bff42c1aaa73f0b796283353f2c1ca"},{"version":"15.0.2","sha1":"a1883ed750f1abc6cbb4f77d99f3f5bd3a5a83e5"},{"version":"16.0.0","sha1":"af5cfb9705a08a9ae387c8674bbe11d5ce886d59"},{"version":"16.0.1","sha1":"b4f38372392cd482c337e8bc04a9d73e27046517"},{"version":"16.0.3","sha1":"9351f18f7b87a5ff8d074d0f2fb4a2d1ae509a92"},{"version":"16.0.4","sha1":"2324bee5607641c82245cce97c76ef80da15b63c"},{"version":"16.0.5","sha1":"94a1dede9d97160c0e9cb5634b9b8b868fb14bda"},{"version":"16.0.6","sha1":"f20e359b66b0f10989f5bd94d0be16922a5abf75"},{"version":"16.0.7","sha1":"beadb1c9286d29e61a60c9cbc169e2735ceedb44"},{"version":"16.0.8","sha1":"815add8c341eb85ee3d398242d42633649a4dbba"},{"version":"17.0.0","sha1":"bd12587f1e35c1751ccf44fdeceb4aff95479d8c"}]},{"package":"play-services-maps","versions":[{"version":"6.5.87","sha1":"4f1a1af9cab90230d887e3a467b91fd40a843327"},{"version":"7.0.0","sha1":"43d9b7d078ac4c17db5d508f264e961d9656f63c"},{"version":"7.3.0","sha1":"9fb8263e511ef58ce11777338881189b50ebfa62"},{"version":"7.5.0","sha1":"7c5da359119458550f8767d0e866e27409b9152c"},{"version":"7.8.0","sha1":"ca46710c956779de92f80697b452151832ba4981"},{"version":"8.1.0","sha1":"6f02ab3027a36822e48cde7efb98242034107709"},{"version":"8.3.0","sha1":"1a25cae41c9db54bc7d4edc3a6ba7b4e9a76e2e0"},{"version":"8.4.0","sha1":"2af4f2848503ef6ddb76ef41aab8c12b14546972"},{"version":"9.0.0","sha1":"e84a5a8a9f60a7b9fd130b0e7d8f8dd972b135de"},{"version":"9.0.1","sha1":"bd5aeb666fa3f914852073837b7ed56113bc3fff"},{"version":"9.0.2","sha1":"c3b50d4ed909cfbff43be034d3060f2dd83f1246"},{"version":"9.2.0","sha1":"fa8f2b38e4b142ac3f54d6c1008f8aa5f78f7166"},{"version":"9.2.1","sha1":"7c95e3453e2d5942db045878f8de26f2c0311aa5"},{"version":"9.4.0","sha1":"b057622f4c3f4d77e46018e1d3d414a7d214272e"},{"version":"9.6.0","sha1":"c4566c28367861482babe63b21b5956fa7fc38a0"},{"version":"9.6.1","sha1":"e379b7be62841df0b6fc32881bcdec2ef0fdaaf6"},{"version":"9.8.0","sha1":"1d734e51b6455075ab34a30959bd81c5f54d6e9e"},{"version":"10.0.0","sha1":"923df43561de4e1713c8f77093584580cd754e2a"},{"version":"10.0.1","sha1":"8254af87174aa8364b112e3106454a7985914cf3"},{"version":"10.2.0","sha1":"aad6129b126af23487d0ff0b566876ea6c3822ed"},{"version":"10.2.1","sha1":"3e995c5d1195a6846334bf370fe948f009eb12d8"},{"version":"10.2.4","sha1":"855aba40149963aed3090d4131dfacf3a10f0cf"},{"version":"10.2.6","sha1":"734c312a19377430f8e7a09490617e864174b1fb"},{"version":"11.0.0","sha1":"9b7cb4545558711b2c51f29dd8495cd26a097f4f"},{"version":"11.0.1","sha1":"6ab2c4409931401e1f9d38cbf1d3a279c67c4b35"},{"version":"11.0.2","sha1":"623dbd6d08bb0a98a1f441fc65606232522abf6a"},{"version":"11.0.4","sha1":"3c02e0e9be8784dd35425cd8c4d79e20d6345d3d"},{"version":"11.2.0","sha1":"15da9265b34a73a8535ad49282dafd3c46ad4abb"},{"version":"11.2.2","sha1":"ec416dc4ad95ac7e4cf1b15cb2dae5d34b6974d8"},{"version":"11.4.0","sha1":"6397c182b3b038a7a73f04da379db3ec54948bc7"},{"version":"11.4.2","sha1":"27f3c98e5a4ed87560fa7684fbaa6734d17c4e56"},{"version":"11.6.0","sha1":"31fdb9314256743f80be76fe51105ad99eff6ffb"},{"version":"11.6.2","sha1":"65c5db63317d03ec60c1f6a78881de4c7f13b666"},{"version":"11.8.0","sha1":"69aee9f63eecbe9fe5ccd76639a5ea566f65922e"},{"version":"12.0.0","sha1":"73943d312728af63c2290862325fd63e51aee33e"},{"version":"12.0.1","sha1":"8f307607a6b990c6d3c97d93e361ce1b63368749"},{"version":"15.0.0","sha1":"32213115a8cdd20a7a04332cb6ed947e32aef11c"},{"version":"15.0.1","sha1":"221d1b74b2521a0cd51b1c366e4828d791f1d931"},{"version":"16.0.0","sha1":"6a2cc6668a6ef0a8fafd768ed2749a44d7cc2b19"},{"version":"16.1.0","sha1":"6fce3e260135e04721bb75112a1b14970c79ee3"},{"version":"17.0.0","sha1":"cc141d4b22571b68101d999a23f003616fff0442"}]},{"package":"play-services-appindexing","versions":[{"version":"6.5.87","sha1":"7d402dd877dc2752bc21c080b5a3d8a111dcd75"},{"version":"7.0.0","sha1":"e91b8504aca19ea3b3f1320c076bad88d065b95a"},{"version":"7.3.0","sha1":"5b9c973c84c418b24937cb006fc6bb611fb981f2"},{"version":"7.5.0","sha1":"8241f7be6796e597c39eb35597ade8a43794cbe2"},{"version":"7.8.0","sha1":"7afb6951f057c4cafd3f0ef9cd4f96729e00abc4"},{"version":"8.1.0","sha1":"4e9578b801a0f7b99b345d0b84a946574732f107"},{"version":"8.3.0","sha1":"504ee7561d47957cd05849deb753e611b60ed6e2"},{"version":"8.4.0","sha1":"661f4c934cc705a51ea787b102e37a4bf367188c"},{"version":"9.0.0","sha1":"b3f1549c232f6f263f011e309e77a1e570b1b02f"},{"version":"9.0.1","sha1":"a3bc9ec2b72c809172143c6d5c0141d7d0cfd4d8"},{"version":"9.0.2","sha1":"4d4905c22b0679c857ee2c54d37547d9bff5c380"},{"version":"9.2.0","sha1":"c1d931b05e4b5c084a623f7563d54a0f504d88cb"},{"version":"9.2.1","sha1":"b85232f3b460c70aa0468855668d2c93346b84ce"},{"version":"9.4.0","sha1":"436cfea75e3cd656c74bb4e364a078fa1fc639f1"},{"version":"9.6.0","sha1":"efbb70e2f2fe1d2cb79164e7028be655cb80690"},{"version":"9.6.1","sha1":"ff76e66b0e8a325f509255bc654a64d44311ae36"},{"version":"9.8.0","sha1":"5592452209727b0166e2cd9f91215b8ac601fc7c"}]},{"package":"play-services-wearable","versions":[{"version":"5.0.77","sha1":"a86d43f0164c1b1911a3c247fc70c4a98b500ceb"},{"version":"6.1.11","sha1":"e93590b50168c75d9f2df709768e92cb0483bf3b"},{"version":"6.1.71","sha1":"84a6080f3ecb2345fe94e709612c8fa71bc6441a"},{"version":"6.5.87","sha1":"7dc99d7962c2513a3df30943d35160fb3594f6c1"},{"version":"7.0.0","sha1":"5d327493f797883de2a20628dd896fc36af334d"},{"version":"7.3.0","sha1":"d359d78a44ebd17e911f2a9b1e5b47473df14bcd"},{"version":"7.5.0","sha1":"67c297c73b007d4a87b8b5b00d657c14b2c8bfe7"},{"version":"7.8.0","sha1":"d5674ca75efbbcd4261f6215db35f9dbe6dc0a97"},{"version":"8.1.0","sha1":"3cecaeddeff9ee00b6708640a16bff2fadd3cc40"},{"version":"8.3.0","sha1":"98ab622fc1760f262c58dd92e5d2137ca10935a7"},{"version":"8.4.0","sha1":"eceed3192de5189ea1d514eeae6306b6beeb68bb"},{"version":"9.0.0","sha1":"236627595230f87fdd84d4dc824bf178ddba3a3b"},{"version":"9.0.1","sha1":"9c78b999296fee852b773804c34077e6b1c46356"},{"version":"9.0.2","sha1":"685d0d0508193fbb3e64569cbf2ce7b67290b1ee"},{"version":"9.2.0","sha1":"54f0ede5c2785867ed2de4aeafd2afea856db670"},{"version":"9.2.1","sha1":"c2fd20753c344f6109fbf3b2f38d2e4dae31d9e1"},{"version":"9.4.0","sha1":"8dec8bb5a013ae5cd9ef5bbbd78c3cd8205264d2"},{"version":"9.6.0","sha1":"368d16d7ce80f61c4dad096cb7e5f9c5e66562aa"},{"version":"9.6.1","sha1":"c9b680935c9b1844799c20c51be810eb469df13d"},{"version":"9.8.0","sha1":"bcb57d168bdfb019069deed9964715f979a6f0ef"},{"version":"10.0.0","sha1":"e1a74d919fa685a25b0e04b1e40ba3d730143099"},{"version":"10.0.1","sha1":"28b406f90795c171007af3d844f403872509625f"},{"version":"10.2.0","sha1":"b109f5719cdb40bd8754ab37b16be18fe6b54d5b"},{"version":"10.2.1","sha1":"ac6d7a00c243e7058e5dbe73b3b8d70cd02f0085"},{"version":"10.2.4","sha1":"23282cb6241643dd1f8ea5cc89f814a71699c76"},{"version":"10.2.6","sha1":"b389364e34a1359d93ca2a72f95fa8af293d19db"},{"version":"11.0.0","sha1":"24acc213d854a205de81f28b84f01d8b3c79373a"},{"version":"11.0.1","sha1":"815bfb96c17fbab125988562f48fb02355b5a633"},{"version":"11.0.2","sha1":"120d23578397ebc6b929b6fe3aa580cb3eac1858"},{"version":"11.0.4","sha1":"6998ae1c5bbb49f9c214abb40acb937719ec4707"},{"version":"11.2.0","sha1":"9604faf4fdff5bfdb3e650141cefe8f6bff8c22f"},{"version":"11.2.2","sha1":"e0d22d59d97f66512a705065ca6c8da596f0d81e"},{"version":"11.4.0","sha1":"8623384ec32fb290880b4f01c05a5e491d781211"},{"version":"11.4.2","sha1":"49fc30d4833a23bd14b7c85769a15daa7750073"},{"version":"11.6.0","sha1":"35548fab88b371ad3ed2dd955e6e3a99fb4091cf"},{"version":"11.6.2","sha1":"e55c6d112f8b4abdf5fa9feb41e047de664bf1cc"},{"version":"11.8.0","sha1":"12dc4a6003344a5bbd2d8ecd842c3a375c72e0f2"},{"version":"12.0.0","sha1":"b7fe337b5a301e86be3fc22bdb41b33258f20837"},{"version":"12.0.1","sha1":"8a31e012b4dfccea2acc6e5c48655eec843b37ad"},{"version":"15.0.0","sha1":"5c5d1d52e347ee16484d02a682a9621d7162d1c8"},{"version":"15.0.1","sha1":"1e7c27a50f63631b6bda1d0e876423c8eb02b92d"},{"version":"16.0.0","sha1":"5e04ee1e1222a053b4eca4d4cc906b647a9ea384"},{"version":"16.0.1","sha1":"35b6b4986f1e1e21deca055ec1687b86c3222819"},{"version":"17.0.0","sha1":"cc4973ffde9efbaccb9dd8c28711c7fae2441fa8"}]},{"package":"play-services-awareness","versions":[{"version":"9.6.0","sha1":"b034143683203d36a5a850b0774e04fa514beb31"},{"version":"9.6.1","sha1":"92a492f3ea3d1bb583882c1ec7f2602fe66253c"},{"version":"9.8.0","sha1":"589963827cad8700ea7ef18a27f464ebda672035"},{"version":"10.0.0","sha1":"c9a6e6215e46e9678aeb6f807f505af37f6373b1"},{"version":"10.0.1","sha1":"d33024cd8640f8fd040e69265effd579076f4271"},{"version":"10.2.0","sha1":"9461edad234413d40e4974d36ea62d1ab5f9a827"},{"version":"10.2.1","sha1":"4d165cc65b5b6dc0516dd873d8ba2dd54e3f1996"},{"version":"10.2.4","sha1":"7bd687c39589c429885859ad0ddae4e571a4a921"},{"version":"10.2.6","sha1":"9f4ba110db3975d2d4e92d959036098ad0a14edd"},{"version":"11.0.0","sha1":"a67b1962f5a78c4e984156141362e5484ec1bff2"},{"version":"11.0.1","sha1":"2f676396f79940e0dd4545e52bec040f688e4f07"},{"version":"11.0.2","sha1":"3b575bee4f27b0a1b92dd50d9d43eef48bddcb4c"},{"version":"11.0.4","sha1":"3547a9a95922499647849648a677cc8d2acb4f02"},{"version":"11.2.0","sha1":"10a3d8651697c3f28f9e8a3e07f45bac7ac94142"},{"version":"11.2.2","sha1":"5d30a7b1b0b1a9539b2f1693f96f2840352105f1"},{"version":"11.4.0","sha1":"5796a9c2db170b3d5551b486559a12422dbbd3d1"},{"version":"11.4.2","sha1":"38ba75d1c8a81373cce6755883c6cd5087151dc3"},{"version":"11.6.0","sha1":"ef8987959c7da7a13cabca46b9283272c4fd78dc"},{"version":"11.6.2","sha1":"5d3527bb118be2772b4272be84f90a7c5d21de57"},{"version":"11.8.0","sha1":"b72edc3c6afe50cc528d091d80012bf76158b653"},{"version":"12.0.0","sha1":"3c154b133ff3bee0305e41c859759805ac49d25c"},{"version":"12.0.1","sha1":"d945270d1a018f0a6bf52bfb56e6bcebdab117f8"},{"version":"15.0.0","sha1":"b761e87629f10bd4ef17361cd3e8866d14e47c44"},{"version":"15.0.1","sha1":"7573dcc336f93214723da749fecdfde4aa8f0043"},{"version":"16.0.0","sha1":"bb6fbd90385554f63427eb739024c8a8609aefa0"},{"version":"17.0.0","sha1":"cb09596efe07806af1ee4d1ac5a476940d0d081a"}]},{"package":"play-services","versions":[{"version":"3.1.36","sha1":"3f81be4751c73fb4f77da661f8cb44adadbb1e35"},{"version":"3.1.59","sha1":"893b99591416e22219bae416dc98cbb03ff1ebc6"},{"version":"3.2.25","sha1":"50529e5d1c2fb04d2f12931711dcef0dd5af51bf"},{"version":"3.2.65","sha1":"73ad64eda4f3aeae7483d4bcd436615d09faedef"},{"version":"4.0.30","sha1":"fe63bb54e908d00a09e2ffcee1932e7d118fcea"},{"version":"4.1.32","sha1":"352bbf914d0af0315a7078b5db925f2a6b81f0a6"},{"version":"4.2.42","sha1":"45538e3900d94e363b1fb8a1787a3c81b61799e3"},{"version":"4.3.23","sha1":"57d2657ac2e923e8481e4c4762d84344b8fef612"},{"version":"4.4.52","sha1":"600e5c8b50ef8b2deb74a43ef5ab46a2422e33c7"},{"version":"5.0.89","sha1":"5f972b4a15cf461bd40ba11916e7fb9b0b2d5836"},{"version":"6.1.11","sha1":"b29f4b849590a5e0feb67118c6ddce967218e00a"},{"version":"6.1.71","sha1":"cbd1453a61b55283970b80779b936c70480b3c4d"},{"version":"6.5.87","sha1":"d94df28dca252066a0f401143799a29f6243e903"},{"version":"7.0.0","sha1":"52aecb53b7e289e4551afb3d3eeb01cbada07687"},{"version":"7.3.0","sha1":"52aecb53b7e289e4551afb3d3eeb01cbada07687"},{"version":"7.5.0","sha1":"52aecb53b7e289e4551afb3d3eeb01cbada07687"},{"version":"7.8.0","sha1":"52aecb53b7e289e4551afb3d3eeb01cbada07687"},{"version":"8.1.0","sha1":"52aecb53b7e289e4551afb3d3eeb01cbada07687"},{"version":"8.3.0","sha1":"52aecb53b7e289e4551afb3d3eeb01cbada07687"},{"version":"8.4.0","sha1":"52aecb53b7e289e4551afb3d3eeb01cbada07687"},{"version":"9.0.0","sha1":"ae85c35cade6a121a7cdf9c43d43a9bf6863b349"},{"version":"9.0.1","sha1":"504911471827fcc5ce16d66567acd11fa9884d52"},{"version":"9.0.2","sha1":"c69bd8bd0b5ec0333340470904986e47f7fbbdcf"},{"version":"9.2.0","sha1":"592913134e458d2488650d50b2491388f2b0e0fa"},{"version":"9.2.1","sha1":"16ce3c209526b60ad70eef4ba426c7ec55741cfc"},{"version":"9.4.0","sha1":"cbaea94ef789eb3a1215aaed233b581ae891cb88"},{"version":"9.6.0","sha1":"5660ce111855c5fd841e4dfb8328405594e08f4f"},{"version":"9.6.1","sha1":"6024352d002998e17968f3b24235b5669c95cb8a"},{"version":"9.8.0","sha1":"cf75d72e09f5b6e3b660de40b68d6631c1e5b5a7"},{"version":"10.0.0","sha1":"2b0f5c4e00c44df6b472113561cbddad021813a7"},{"version":"10.0.1","sha1":"c30569eea40fbe2e0961d13feb552eb9a2a1ac0e"},{"version":"10.2.0","sha1":"74604521207380b5bd9244bdfcbe1d349f8494af"},{"version":"10.2.1","sha1":"a43ce948377ab84ad0dd34f1779d36c2863a53b3"},{"version":"10.2.4","sha1":"6f275635d80706c768c294a79634b7193a969386"},{"version":"10.2.6","sha1":"90c85d7393575c55b4aab27751034d61e2a878ad"},{"version":"11.0.0","sha1":"d8f03551bb743e78b62841faa6c009ca4498aeaa"},{"version":"11.0.1","sha1":"425553e629d74052baaacdae698bbf577e7a7f55"},{"version":"11.0.2","sha1":"9aa1610affafb6f80eeced2af0196be7d1425d19"},{"version":"11.0.4","sha1":"72752a0a038eca6c986ab1114645965c2719e523"},{"version":"11.2.0","sha1":"c8a399ff6f910cb8676789b9140f8ee0783da209"},{"version":"11.2.2","sha1":"c7ba3ba9878fb9dbfe1376de3170d0548542aca8"},{"version":"11.4.0","sha1":"6cc88c1164f287af76ce8218a88166cfcc917e3"},{"version":"11.4.2","sha1":"bd862599c030eed3867a36f99d1b47b15ebdb8e3"},{"version":"11.6.0","sha1":"4b7e8c4d2e47deca7b3484c379dc43a4aaab7f74"},{"version":"11.6.2","sha1":"f382e1cd6c8f30ea4c884bd7f8cd33b0e8c53d06"},{"version":"11.8.0","sha1":"31547b7e6a7466755749cf185bf4d29204e6c3c3"},{"version":"12.0.0","sha1":"46b1faa2fbfcb733854b7bb8fac2921e7ea3ff62"},{"version":"12.0.1","sha1":"9829d1dd3e4ed222ef36d6d19b759ece46f8de9e"}]},{"package":"play-services-nearby","versions":[{"version":"7.0.0","sha1":"f821abeec1cd305a3f4b72876afa8da20994e72f"},{"version":"7.3.0","sha1":"a1053a18653fd06d72073ecc6d0886437aa19dd3"},{"version":"7.5.0","sha1":"1c3c59f2fbbef187951f58f0fd44077fee51d804"},{"version":"7.8.0","sha1":"76a53739cebe1c788b76acc019b70c60c9c8921d"},{"version":"8.1.0","sha1":"7a49a9c4db31e217d1d62dee9debf483cc4e5844"},{"version":"8.3.0","sha1":"ee7c87b53e016044d6473dae013186a577adcb7"},{"version":"8.4.0","sha1":"34eb35b043719e60879b5312972003d94c212943"},{"version":"9.0.0","sha1":"f4530d971d7df8a0dedce18245a7a0a377e2f9a8"},{"version":"9.0.1","sha1":"8cbfd78c1c03897f08491e836bad799b33699b10"},{"version":"9.0.2","sha1":"463de560e4deb939db9aadff8d29f938da1fdfcb"},{"version":"9.2.0","sha1":"c0df6dd460966cd67a62155f5ccb3b7f612e243a"},{"version":"9.2.1","sha1":"cddd89533a9f5462751456a11c62f032c9a05de1"},{"version":"9.4.0","sha1":"32fb56d52d1c0d1aebb8b23365e481846307b86f"},{"version":"9.6.0","sha1":"c236e5fa551e0133c74c44e6b5907ce8ce460af2"},{"version":"9.6.1","sha1":"c64d599df042980385fae88fa062ea8e771e55b6"},{"version":"9.8.0","sha1":"9edd639796249125c59de519f8326c9edffae5d5"},{"version":"10.0.0","sha1":"2cb94964870fcd69702b1d659e281f1ae606b9c1"},{"version":"10.0.1","sha1":"45c28b05dc457797bb4f56c46d7ed46f3551cc4f"},{"version":"10.2.0","sha1":"bcf04807be5aa0c55b49a30a1bfba857f1d219e1"},{"version":"10.2.1","sha1":"8a6bf16ede560b42860bc2647301acd195c8115b"},{"version":"10.2.4","sha1":"36e423cb41f30feea046fb4530d9940a8b232c18"},{"version":"10.2.6","sha1":"b6c286f0a2cc59fe87a16243b5524c0c0f2c5075"},{"version":"11.0.0","sha1":"be112594450290da0e13038439bdf96580210ef0"},{"version":"11.0.1","sha1":"27dbe4672454ab6a4dd4f6fda09d7bbcd0e3dc2b"},{"version":"11.0.2","sha1":"d171bc78d05576c363cad04b11c1a01f07202be8"},{"version":"11.0.4","sha1":"91dd672fecc5142623795928983905269e822827"},{"version":"11.2.0","sha1":"c6f7bc0344272b23e071db227a8eb7aab4a42f84"},{"version":"11.2.2","sha1":"5cbe18afc760066874ee6f04dd3690e53afbed6"},{"version":"11.4.0","sha1":"e15d15fe49c968863e65b1d24395730b21f7aad2"},{"version":"11.4.2","sha1":"84de0c2699f2fd292fb38395915bba8cc6d98f02"},{"version":"11.6.0","sha1":"799c5e9e9022b7adfa85eef39c3afc9f0369b4ad"},{"version":"11.6.2","sha1":"59b99861011e2980964c75b46410c6eb662b5569"},{"version":"11.8.0","sha1":"44cb6f2e9635ae6120508755a70cc0fddffcb945"},{"version":"12.0.0","sha1":"4eee0154ca6ac60c93e52427fe93b80099e69f98"},{"version":"12.0.1","sha1":"a5b9b98231c8e8e06c0e9fef1a7c658888178ab6"},{"version":"15.0.0","sha1":"1d707a65f4bdb86ba7cecc88f6f2b87ff1440949"},{"version":"15.0.1","sha1":"a586e88dd8dc3473e8f6cb74a7e576da919efe99"},{"version":"16.0.0","sha1":"5e96d1bcf3921789184fde6704b23454674e160d"},{"version":"17.0.0","sha1":"8e85e61b1218e923eac3eafa0410b115079df292"}]},{"package":"play-services-basement","versions":[{"version":"8.1.0","sha1":"997dfcce730a948ff7a59d20fa38161a7d513720"},{"version":"8.3.0","sha1":"20b4e3305ce4f680505d6c6dea166560cd1a5326"},{"version":"8.4.0","sha1":"d4bea88510eb3c496d8b2b9e8f90d09fab570176"},{"version":"9.0.0","sha1":"4fa2552fe04bf6dddc8f65ec416d0c4380a38012"},{"version":"9.0.1","sha1":"98ced07280b9849c81e244e343cbe38924a8fe40"},{"version":"9.0.2","sha1":"f05f03f42718732fb96e54655c35bc3a56f2a98c"},{"version":"9.2.0","sha1":"bab9d5150efb01084556edcb08592975ad9250e1"},{"version":"9.2.1","sha1":"be2827cdba2f9879a812ae9ac45f169e9adacbba"},{"version":"9.4.0","sha1":"ef99187a6d4a324da9ac2dedcda3e2fdfbd6d422"},{"version":"9.6.0","sha1":"3e5b874cdfc131ebcb68e5a2e82a2e1427cba0b0"},{"version":"9.6.1","sha1":"120a1f859babc6208041022011f93d7f27b13ddc"},{"version":"9.8.0","sha1":"37393caf19b07e98893485ef702abb930b26c2e1"},{"version":"10.0.0","sha1":"cef403b9996e750f256a24a9333d7c00a45dee57"},{"version":"10.0.1","sha1":"f5393d8a0e949be121e08fa063e70c02efd51266"},{"version":"10.2.0","sha1":"fbce115fcc36c00db67436bfce3f9b797ee10a43"},{"version":"10.2.1","sha1":"fea324a2725b9ddfde2b049af7103a2192601bb6"},{"version":"10.2.4","sha1":"638d8aa7e03977f4aa3d716ff1c74da077488de0"},{"version":"10.2.6","sha1":"41fb7cb805d8864c75f437fa4a3c8a1824c69aae"},{"version":"11.0.0","sha1":"956a95f81aaa98dc8b458ef10db17f262aae4296"},{"version":"11.0.1","sha1":"5c6241c3131fb6570297b5d2606a7667d4b6e016"},{"version":"11.0.2","sha1":"918e99d428d0fec1baead95ccab51af7a1b407fd"},{"version":"11.0.4","sha1":"58734a1d0356c3f6a8bffbf4504eb57e0c87ffb2"},{"version":"11.2.0","sha1":"49a9788da1781cfecaf919506a715e0fe56c13ee"},{"version":"11.2.2","sha1":"b4eb8b32a905d1532dde490b85dfaf8afc843c68"},{"version":"11.4.0","sha1":"74478a02deb75d4cddf3a966e332410a720e7103"},{"version":"11.4.2","sha1":"1be5a5f5b021defb4eb4d642d1386724e298c2b6"},{"version":"11.6.0","sha1":"d772cb791b88129f6fe22d9c51b49340b5c17db5"},{"version":"11.6.2","sha1":"781da12a4e4664be68060bfafb9b5b25d8e0a9a7"},{"version":"11.8.0","sha1":"19cf0832c2926e25d6a5d55e187bcc21e0900b1b"},{"version":"12.0.0","sha1":"fc1a37fed22fd081a75e37e54246c642d6c65964"},{"version":"12.0.1","sha1":"1283907c253f48310cc2217b4e5548a7537860ef"},{"version":"15.0.0","sha1":"62274fce4ac2e80bfbc4ca3f44f4a3a688a1b1bd"},{"version":"15.0.1","sha1":"6ddcab43dfe529c7ae2f7ba2ce886fb2d5b66597"},{"version":"16.0.1","sha1":"924c32f9c032821afc94dec82c0f40e81310f67e"},{"version":"16.1.0","sha1":"4e03a61a346ae963fe1856938e62c1b810de37ad"},{"version":"16.2.0","sha1":"6341a60bee89770241aefc8ab2ae0490e1b5b934"},{"version":"17.0.0","sha1":"f94e0b893e0d5d2a922577986c8b2a31fd79f634"}]},{"package":"play-services-tagmanager-api","versions":[{"version":"9.0.0","sha1":"5030375d51291dcdc26fcfbee42cf27e20302324"},{"version":"9.0.1","sha1":"1aa105720932b9675bab2d4581f4efbe238f88ae"},{"version":"9.0.2","sha1":"7d8863207b30915d97bc4339755ad382f5006981"},{"version":"9.2.0","sha1":"13d6e6c8410e792d32328028008ed9c588fba086"},{"version":"9.2.1","sha1":"5372371528e25882cad8c4853d0bb241cdef1e56"},{"version":"9.4.0","sha1":"bdf02a40cc1d9e55742df674d1f0c2290a1eeebe"},{"version":"9.6.0","sha1":"614ed2dce9f0ca556a7cb27dcf34b07e12b59b8d"},{"version":"9.6.1","sha1":"bb3c37238928431037397e7884b1ba0ec6a88425"},{"version":"9.8.0","sha1":"4ed026faca3bf825d7feed79595604a618282915"},{"version":"10.0.0","sha1":"82ec32e024fbfb307b1fee4098cc398324c7a3d"},{"version":"10.0.1","sha1":"e0c73183f27e81034dddd2209c4e4b49948566d0"},{"version":"10.2.0","sha1":"b6d4affdf2fff33507720ccab749b26caa8e3a0b"},{"version":"10.2.1","sha1":"67b0b218ede2644df79b81f32096b71fee96ef1c"},{"version":"10.2.4","sha1":"5ab7b66e63d4f35d0c024bfd01be3e543a2f2039"},{"version":"10.2.6","sha1":"f034cf4fd55a97c9497dcd8ac8bd0aa8c05f48f8"},{"version":"11.0.0","sha1":"cc790f3ffd5b03cd0aa54a86c652e9f9bbe04139"},{"version":"11.0.1","sha1":"42d3793e2f1fa55a6ca7bb7d0454f7b7cac0b15"},{"version":"11.0.2","sha1":"734847e7ede6a8f9f669ef5e6ca20469af5bd80e"},{"version":"11.0.4","sha1":"5c514d6241a764d1a51472501a572b55e977e2c5"},{"version":"11.2.0","sha1":"4e33810835c25ebce05a661cb5d9a48970313fff"},{"version":"11.2.2","sha1":"f31edc92cb46e5bbf0058937d2d9dfc87bf5c3d1"},{"version":"11.4.0","sha1":"14a39c5a5be69062f26c6a3a73a62ab1d155401b"},{"version":"11.4.2","sha1":"b4e16dcd6125d50912a078af5c01bc042fb077b7"},{"version":"11.6.0","sha1":"2b10dd6b24dfa1613dfcd85148cb0526d6ef037"},{"version":"11.6.2","sha1":"b46f637f0e77c49194316c501ae7ddca93628258"},{"version":"11.8.0","sha1":"ccd20df3d9cf844a5f91dc6f541778ded5a81fe9"},{"version":"12.0.0","sha1":"e38d247b59ca8f0f7c889c9acebf281ffd3ac10f"},{"version":"12.0.1","sha1":"e1f9dc7b52dbba750fbeb589536f8d03f570f767"},{"version":"15.0.0","sha1":"f1928d40b546b861f4d19e8aa7d4506b2cb56fc7"},{"version":"15.0.2","sha1":"f9f89dc13eb05dcdead13a997d30d31fe5aea562"},{"version":"16.0.0","sha1":"fdcb2019dd4c87c85955cb3fd25b02c3d6445c51"},{"version":"16.0.1","sha1":"ae332d78e10aeb6b3e03ae535370f7c3fcf4ae26"},{"version":"16.0.3","sha1":"b62f453685381c501f36c1fff8033f0152d18bfe"},{"version":"16.0.4","sha1":"b108211317622943d173a2bf63f479f2dad39ab"},{"version":"16.0.5","sha1":"7ac7c35c5b6f241c3ae8247176fbdd90cb7047ea"},{"version":"16.0.6","sha1":"ea4d02fca88dcddfb2501be238298293a4176ae9"},{"version":"16.0.7","sha1":"7f22d83f2d4593f253de25618ca124e6f962f165"},{"version":"16.0.8","sha1":"55c13256e1b195b0baa86d67d1e0942d4574dcf1"},{"version":"17.0.0","sha1":"c67d52999ad80b64999945f95ba9ecc1d6a00689"}]},{"package":"play-services-tagmanager-v4-impl","versions":[{"version":"10.0.0","sha1":"2798bb757f6932c38aa58c414c4b89b3c1a79605"},{"version":"10.0.1","sha1":"990e9c6a0dce23620b89b2a3639679d3dcbb857"},{"version":"10.2.0","sha1":"27e6bb67630ecb78d60e6507e03f1bc67d00bc42"},{"version":"10.2.1","sha1":"45cb3ca1f60dd4cda9edcfe33aa96a0b76cee20e"},{"version":"10.2.4","sha1":"f3768b080d5f9eb7ea6d299e729f265fbd384691"},{"version":"10.2.6","sha1":"75ec418764cfb88c076a1726378935fcd04220ac"},{"version":"11.0.0","sha1":"1c6122ec9bf7c65a65c447aec53a52a27154b669"},{"version":"11.0.1","sha1":"2c17968908124fc71ef949510f3d8db022a71e3f"},{"version":"11.0.2","sha1":"a4554b4723a600bb37744a283c979eb72796be60"},{"version":"11.0.4","sha1":"57c1193d0293bacd187fea51fd93318ae78d2a76"},{"version":"11.2.0","sha1":"b13da1abfb2688e6d66c983b29202817283d5b3"},{"version":"11.2.2","sha1":"3ea8c2fd1fc381d0a9053b64b279fac4a83b8a5e"},{"version":"11.4.0","sha1":"f9cb4bfca8093a23c9f791551adfdcb38eb748ba"},{"version":"11.4.2","sha1":"bb548cf9ba09b4e517187a51e4fb852f2b5d62a0"},{"version":"11.6.0","sha1":"2971bfe889956606044b609bccd4aef5680d7df1"},{"version":"11.6.2","sha1":"bb4c6fb0100befb51835c3a8aadc12a4460d2e47"},{"version":"11.8.0","sha1":"ca2ea0d20300736e2316917e68c2c342dc942ecd"},{"version":"12.0.0","sha1":"2c249aae92e28cae484a6e6e71c3c019b05c7793"},{"version":"12.0.1","sha1":"d8dac14f248653576e70d59da80c091ec038203d"},{"version":"15.0.0","sha1":"1492c625aeef40f97661ecc8cc9c2a64967ca485"},{"version":"15.0.2","sha1":"18bd62f6524aea4097a9d47714f9c581d66904d0"},{"version":"16.0.0","sha1":"70cd2256f52f31bc23de3d964dc76ca1b80619fb"},{"version":"16.0.1","sha1":"d9fa6e26b48a5b7ef35d0143fac9245c5b016d5a"},{"version":"16.0.3","sha1":"f6195296550891c9f006e55962503af5909025b"},{"version":"16.0.4","sha1":"9dc0e0c383fed246930a3eacf2f273c866564506"},{"version":"16.0.5","sha1":"7d79c91e181bce6291d6b5b2e9a5f5619e5de95a"},{"version":"16.0.6","sha1":"6b62e45aa48331112d4828c1e66fc366caab85ee"},{"version":"16.0.7","sha1":"d3d7b71de5aba0f996111f227f6bb4f58846b83"},{"version":"16.0.8","sha1":"c2879982a7ee77d4148f5d1f035147ff0b39857f"},{"version":"17.0.0","sha1":"97b317fceaa23003a3d334d88f880cc1afdf3498"}]},{"package":"play-services-safetynet","versions":[{"version":"7.0.0","sha1":"57a6b4a86ded1dd2211ab201875703de41a12ec"},{"version":"7.3.0","sha1":"b30f30c6470da1fafe3e8273dc58e90b1a039aa4"},{"version":"7.5.0","sha1":"583320c1363ed8398e9d57a426440e4be92ad369"},{"version":"7.8.0","sha1":"385544b15e47d00379f1510fc4e7e5e8c90ed98d"},{"version":"8.1.0","sha1":"5bfb948a1cd39cd7137190224ee9fc589fe497ad"},{"version":"8.3.0","sha1":"832da907ce22491521575db2ce1920cd908838fd"},{"version":"8.4.0","sha1":"56e79f6f5975f49c3eae3682f8889c6fc035762f"},{"version":"9.0.0","sha1":"77a44e0c1ba59355edd01a5f9805374a8fe8ee26"},{"version":"9.0.1","sha1":"cd045a7c3bbf836fa2b74dde48441bfce9ffa225"},{"version":"9.0.2","sha1":"d71aefed13955600b9d6f654c19d3c5657472d35"},{"version":"9.2.0","sha1":"86581971abd0814a81907daf78f1f7c7951fa24b"},{"version":"9.2.1","sha1":"4b068cc7e7299877a25f5d5f261b176810fe233a"},{"version":"9.4.0","sha1":"fd8f23a4f5ef3848e50debfdd5e2af30898a3131"},{"version":"9.6.0","sha1":"febddb57c4d1aea149e1f1a87fb54b4a8a115ac3"},{"version":"9.6.1","sha1":"961eed5aacd1d0fc20a2f388a9d8233010e78fad"},{"version":"9.8.0","sha1":"cac33637e0b2d57dbe51aa27ebd1d13f15c3e65b"},{"version":"10.0.0","sha1":"a0adc361a82c4fbc2985e658c6b87447402a2a8c"},{"version":"10.0.1","sha1":"b3fa3fb6b3083958e47ee5c6ad3b010c84f544cc"},{"version":"10.2.0","sha1":"acf77c3c7f998f61342b4838564dc1997fb94736"},{"version":"10.2.1","sha1":"acddc594cf527aaa9a160793102f62df1e93cbb1"},{"version":"10.2.4","sha1":"3000d722736122a3f9e334a082d01ea55a35a61d"},{"version":"10.2.6","sha1":"bf68b4258693b804f6907f1ca2793277ba87e5a9"},{"version":"11.0.0","sha1":"3f6e6d50327a803c22ad03529242dd06003ce68a"},{"version":"11.0.1","sha1":"ac9860154a93a57955a4de52c3c3389a36e3ba8d"},{"version":"11.0.2","sha1":"f43f90923df5a5fe158ccb39057751f598f914eb"},{"version":"11.0.4","sha1":"2559f5856d6340a24207a98bd9039974802e2df7"},{"version":"11.2.0","sha1":"6deef0b5dda9bc72288f76207896b526767f5649"},{"version":"11.2.2","sha1":"843140c46eb825b1cc802dfdbd3fac4c0b294da5"},{"version":"11.4.0","sha1":"45380c8d83e48610c9db31068dd5b9059cc31a74"},{"version":"11.4.2","sha1":"fe81c7879ff74d77a21885ba2c1026fa80050631"},{"version":"11.6.0","sha1":"6eca76f57523086b8e3eeafe0ad93ee0e0e2a56b"},{"version":"11.6.2","sha1":"2af12ded498c660723fa96d39bb711e4c9e8f4aa"},{"version":"11.8.0","sha1":"25a486bd8fb3c83a89fed123cd3ec3976da0d52e"},{"version":"12.0.0","sha1":"e5368c9d5ef703a074443fe86aa9351ff3945bd0"},{"version":"12.0.1","sha1":"d0f8987041d352faa4ed074313dac6f76cfbcc9"},{"version":"15.0.0","sha1":"f4c55de90ecabbb85502b6211c956143c2da9e98"},{"version":"15.0.1","sha1":"a97639898a437fee40317feeb457abc4f98136d5"},{"version":"16.0.0","sha1":"1a5cbc8e94fcd5365bc6b1fc832065dc8bcacc7c"},{"version":"17.0.0","sha1":"bab820dda118774cf6e15a96219e1ce82c33b118"}]},{"package":"play-services-plus","versions":[{"version":"6.5.87","sha1":"ff609a6b191ce6aec71efe3128c9975b5f713356"},{"version":"7.0.0","sha1":"31600bbaebea016c43ae41f1483fa39d30e290ed"},{"version":"7.3.0","sha1":"31efd38f569a9bf0d0331d95cdf489ce306fb0c7"},{"version":"7.5.0","sha1":"40cb2aa92b080cdcf5432231956b086a606bdce3"},{"version":"7.8.0","sha1":"6b65c1558df87b0bbd6903a2b10318a97eedf0b9"},{"version":"8.1.0","sha1":"7c64408cdac040bc258c6966da07c6bb01827389"},{"version":"8.3.0","sha1":"eceaace76b7e58b4d06ce4c7e16eff002ca79b70"},{"version":"8.4.0","sha1":"e5da63cc006fdc79acaeea60518cff78dc605a61"},{"version":"9.0.0","sha1":"a32ddfb192e38248f6e9d7e59c04d86a9835fea1"},{"version":"9.0.1","sha1":"864f5c605b593caceaf1374d0cc57de9084cb969"},{"version":"9.0.2","sha1":"82956a3454cd6a8e090a207c274783f6252b9718"},{"version":"9.2.0","sha1":"698069eb3dabe0086cf6d1d5bb96e86b68299e90"},{"version":"9.2.1","sha1":"a94af4aba98ace9e9a2e6636b2453a23eece6452"},{"version":"9.4.0","sha1":"909f6afc2da0835d3647e68d4f828f87f9f6915c"},{"version":"9.6.0","sha1":"4fec226831fccf2914841185af49dbf693c909ce"},{"version":"9.6.1","sha1":"de2779895d8a15c08150f7c2a8fb90b7f4e8868a"},{"version":"9.8.0","sha1":"9fe79699338b57064f16cb6f7bf6360ff598bc24"},{"version":"10.0.0","sha1":"6b41d6ed9e645fb0a209e165732d817019fe1951"},{"version":"10.0.1","sha1":"335ff08cfb98e30814a5570b60f9d5bb8234d84b"},{"version":"10.2.0","sha1":"be922a557c32c8521f30905f460873f8c54d3ee"},{"version":"10.2.1","sha1":"af782dc88959c31aa6abf83eafd738b600b2d1b5"},{"version":"10.2.4","sha1":"9bf05fcef4735878cc9fc7e9998a30484653e688"},{"version":"10.2.6","sha1":"e13546bfd5c9ce9f6c39c125cbe12897a791b14b"},{"version":"11.0.0","sha1":"e1c3d0cdf5c9a188ca636b411c43fe6ca632a9b6"},{"version":"11.0.1","sha1":"40d455643ac2b817f5da3f4a952c677481cd56a8"},{"version":"11.0.2","sha1":"a624a96e980f7205477de25e97cd003d6b0738bc"},{"version":"11.0.4","sha1":"6004e0335541ae51df3c25521f06ff76a192cc71"},{"version":"11.2.0","sha1":"23550bd50e59833b225e3e10d4a11059e9c9b957"},{"version":"11.2.2","sha1":"60d9992cd882cc89d088497e1a1193292b66ab09"},{"version":"11.4.0","sha1":"5d86a38376beed3f6edaa45ff69714534bc39b9e"},{"version":"11.4.2","sha1":"51eb196a361c998d15732eccfc471c9b140e27ae"},{"version":"11.6.0","sha1":"1225856ee018d69fb82cbdfd4136d9004245b447"},{"version":"11.6.2","sha1":"68729e81b2e279e88e75990ea9d70b770ea9829"},{"version":"11.8.0","sha1":"800e2781a3292cfefa7e61485f2962ce34fe0d26"},{"version":"12.0.0","sha1":"3db5fc01c3f17694392865447b0a28096b9bc190"},{"version":"12.0.1","sha1":"fa6321e08328d970461f17ee3d879f01cf1e0fa1"},{"version":"15.0.0","sha1":"28c1c2a5df07eeff54cc32d009f5c5af33ce32b6"},{"version":"15.0.1","sha1":"af85c8ff3029c3e27aed939e92be3dd616c0a7d5"},{"version":"16.0.0","sha1":"81812678d9ea50bb9537a56dd93dd772a1b74253"},{"version":"17.0.0","sha1":"1f52eab406369caa72b48f130addb3415d6aa8d4"}]},{"package":"play-services-base","versions":[{"version":"6.5.87","sha1":"a94b2ae7cdc3e31d01f485f2d8d93a40d18798a0"},{"version":"7.0.0","sha1":"ffe1d4c9133f1c8113066f7d0605796c861a1014"},{"version":"7.3.0","sha1":"536afc1b9747588af8c69b0fe466c15d0f4964fb"},{"version":"7.5.0","sha1":"30e21884cbed2a279db2d9725e835e2070be76f9"},{"version":"7.8.0","sha1":"f491e43048054c8a658dc9ac035550c1aad799a0"},{"version":"8.1.0","sha1":"6ec5b3f737b28a64818b5d245d839e2290994a49"},{"version":"8.3.0","sha1":"42cc2ea5db7853f149ef25c703e77f6a62d41288"},{"version":"8.4.0","sha1":"f68a3ab5ac78676f7dcff940216e7a6e63327c28"},{"version":"9.0.0","sha1":"c40114b1e0051789346d843cf5a566c891f037c5"},{"version":"9.0.1","sha1":"15277dc11e6fb5fe0f24fd8fb9e4cf51e359d6aa"},{"version":"9.0.2","sha1":"2b84b8d868ad28fb483936d6d35580d14e2b0b39"},{"version":"9.2.0","sha1":"3b4bffa495f0fef31c3c78b6b680d0fa20d77ec"},{"version":"9.2.1","sha1":"edcebf765c840406181869c68d2ac8a0b26ad09a"},{"version":"9.4.0","sha1":"1d8ad22865b748fa5a16d7d0aac5559d2ad17cfc"},{"version":"9.6.0","sha1":"6548f4ffa158fded0f6c81ae71743246bc267a7f"},{"version":"9.6.1","sha1":"f2f4d2e0eeec19c07034c4577bfb1f972902222b"},{"version":"9.8.0","sha1":"250f5414c1ff7ed94b9941b73d302e09c7fd0c5c"},{"version":"10.0.0","sha1":"5a65445897f331f7922126ac25cfe0db639b90f9"},{"version":"10.0.1","sha1":"602d0a4cb4d2d4d837282aa3c4b9def5c01fdf04"},{"version":"10.2.0","sha1":"6ff1d66ac5ad4f31b1a89773acc8f0557ca451a5"},{"version":"10.2.1","sha1":"f180cd80cf389fd6cf02dd4ebdfe3146ab8d8fe"},{"version":"10.2.4","sha1":"926321cc399b38dd8ead8852f1497cf20a0ca8ff"},{"version":"10.2.6","sha1":"8fd3ed3711d1e16464554ee78a27f86c8a4ad23a"},{"version":"11.0.0","sha1":"c85287c4de81d4727d3068085589d13477c387fe"},{"version":"11.0.1","sha1":"4a85f2bd93620c8ad4f5fc6cf3f9ecce93c9c04d"},{"version":"11.0.2","sha1":"dfeaa5e99acc3f43928511b10371e912ac709499"},{"version":"11.0.4","sha1":"9f354dbe31ddee95490573343b21ba6150497fc4"},{"version":"11.2.0","sha1":"56b6afc3b47b78bf01cc18ab36e8dafb2eaed59"},{"version":"11.2.2","sha1":"81ff04149b5e055ca80e8782979b5ed916429a7c"},{"version":"11.4.0","sha1":"92cb3430205a7667b139b579478970bb34301c7"},{"version":"11.4.2","sha1":"30b7f653452d6fcd1a462e77591bda4c29cad7cc"},{"version":"11.6.0","sha1":"e23ba52c85b04758512176778b0c5ab64b453662"},{"version":"11.6.2","sha1":"e018c11ddf8aa75901cfe9f3777d3ee257e41dcc"},{"version":"11.8.0","sha1":"b07208c0938ea1762a21d60c51701d224706f36f"},{"version":"12.0.0","sha1":"2e928f18f3739d259e3a601fe9d06913381fed58"},{"version":"12.0.1","sha1":"5269b0c6dbde486e7508a41da357e04e87109450"},{"version":"15.0.0","sha1":"21a3b86222a8d12c11880feac7e79184baae40fc"},{"version":"15.0.1","sha1":"8606db18a16dc052a4632448ee462cacd37eacd8"},{"version":"16.0.1","sha1":"8f06f2d24b1f01db76a0a226b544d0dd7937b32e"},{"version":"16.1.0","sha1":"95ae8ec6dc6f9204362df2966363cbaea1a649a4"},{"version":"17.0.0","sha1":"7fe5a19c8a10b304800a0dac51ffd7ae0acc0648"}]},{"package":"play-services-iid","versions":[{"version":"9.0.0","sha1":"63300143deb07686e3e9288d861a2f304871e0a4"},{"version":"9.0.1","sha1":"788771622b00f9da41be7ad3c03d5b9d995424a8"},{"version":"9.0.2","sha1":"c7e70d38f6401270b4c4a9b88cebc20c112bff7e"},{"version":"9.2.0","sha1":"6f7c8872a1cf1a3285aa8192e120949adff4b4ee"},{"version":"9.2.1","sha1":"730e0b84f337f8e9507ffcf16f15aaa860c8521c"},{"version":"9.4.0","sha1":"8e31c6b4ff4aa7b29ca354f4e11405f6f8a54e06"},{"version":"9.6.0","sha1":"cc74a4800d8aac1b75de20395b960f74edc7ec0"},{"version":"9.6.1","sha1":"c08c0744aac90d178d0a06313c197b1ba436112"},{"version":"9.8.0","sha1":"e5c819663005ed3ad7c3a63bf883590df6003975"},{"version":"10.0.0","sha1":"325ae9416482fbf370d6e475980c80107e080ceb"},{"version":"10.0.1","sha1":"4299429bbca47c4d969d568f8c1364936a0a7751"},{"version":"10.2.0","sha1":"9730480d4b359bf49162a79043d167f652a83f80"},{"version":"10.2.1","sha1":"efda89c1a25f1e8757d6f6a23c6697992ac11d89"},{"version":"10.2.4","sha1":"8e789169f6db5b078c5cc0806597b6dbb21d4260"},{"version":"10.2.6","sha1":"96f918270093d87bd2342f8a2235ca43e26c7c83"},{"version":"11.0.0","sha1":"e56cac866ba639ac2dd72dad3cd25772a17a4d1e"},{"version":"11.0.1","sha1":"382dc2f571372448e21bab38a7cfed5a78ee15be"},{"version":"11.0.2","sha1":"e012b372dc3428be4ded9d52b24112f5133b5ea"},{"version":"11.0.4","sha1":"95e86efbbe5348723b33c316a9c9375bd8b1c93d"},{"version":"11.2.0","sha1":"537351c84f7127f1b31855ceb935d4743c45a08d"},{"version":"11.2.2","sha1":"b622907d0c6d9ec5fdc2277d67357ce7c8695b2b"},{"version":"11.4.0","sha1":"ef9085e01e15cbd32b7264cfdbac644eb55f67fc"},{"version":"11.4.2","sha1":"93e6f455edb16f090f23b3c241df2c82bc875283"},{"version":"11.6.0","sha1":"8cf59257d7716c7795b2ef1c1b3254c6ad3b5a3b"},{"version":"11.6.2","sha1":"cd9baf49954615d64af0f22b31cfcda4154fcc31"},{"version":"11.8.0","sha1":"1dea070512840f3b766232b710465b70d7dd2f16"},{"version":"12.0.0","sha1":"ebed05fc6f2745d52a03cebd75609a9bdc6dac56"},{"version":"12.0.1","sha1":"d09a8e3b164331e6eacbd69dab80848775338f32"},{"version":"15.0.0","sha1":"3623a971d8f3d903716db14bc11f34b58b35e292"},{"version":"15.0.1","sha1":"8829110a05d17ed7a5d22547ccbd5f8e9e8c1fab"},{"version":"16.0.0","sha1":"f514e1a9836b413040fcdbe97df13918a1cb13de"},{"version":"16.0.1","sha1":"2f41b1d1fd42e45c4643d0930efdaebf754d3f26"},{"version":"17.0.0","sha1":"5dc043b659c2ed77a937c514b06532049fb54bc1"}]},{"package":"play-services-panorama","versions":[{"version":"6.5.87","sha1":"302f7f10bc5646264b380652a9ae9ed2668c7004"},{"version":"7.0.0","sha1":"7a145cc4956bb23c36c8272b13433d98146e8cf0"},{"version":"7.3.0","sha1":"f8700edbfc476a838114f54c87e56d9770da2368"},{"version":"7.5.0","sha1":"d74ffcfbede3eac0e43040de8dcacfc97430e6d5"},{"version":"7.8.0","sha1":"e87949705c296594f9531f5c26ed46ddd857033b"},{"version":"8.1.0","sha1":"432d03ed3afbebf6e55de754693e8b53f63c18b6"},{"version":"8.3.0","sha1":"7ba10ad80866a209a4303cd0819bcf4fc59371b4"},{"version":"8.4.0","sha1":"8eb098a289e03db700a0b7f7ab8e104f3d045185"},{"version":"9.0.0","sha1":"bdc351f087cd14dec511fac5acce36823c3d760f"},{"version":"9.0.1","sha1":"b10fed8e5df65f2f0685bd6d492d484a220d5f16"},{"version":"9.0.2","sha1":"762ceaa2784468f719fb8dcc5109978818171559"},{"version":"9.2.0","sha1":"2dd869dbb4026de9b55852756bc7591a1b00a015"},{"version":"9.2.1","sha1":"18d43d6240c01b29fbafd5aa3f5f6d67983243d9"},{"version":"9.4.0","sha1":"6c1a47360a28b255acbfa459e863dac9085d1da7"},{"version":"9.6.0","sha1":"2bc5e38183f3760b9505548ce03d4c610248bd08"},{"version":"9.6.1","sha1":"17c3ce0c334f21824deaf054842bbc437f7b7b43"},{"version":"9.8.0","sha1":"f8d61bf6629eec96ea69879ed271386a27c199f8"},{"version":"10.0.0","sha1":"1753b3fb4929b74bd65786058a89413c975a4e9c"},{"version":"10.0.1","sha1":"58cd28ed96baa556592c7d99cd6bcdf1c637ebb7"},{"version":"10.2.0","sha1":"1902d47c2f55d31d0c3dcc5a3e5fbf166992c86d"},{"version":"10.2.1","sha1":"510f90c7687dae69a7e6e21344295937ce863f30"},{"version":"10.2.4","sha1":"630cb47a78f4c818306526d7fe1817e411bfce1"},{"version":"10.2.6","sha1":"bf9cb09f1f36bb8e8384d2f26119bf77925033e7"},{"version":"11.0.0","sha1":"45f7d21e6bac63bfe37c1efe86a133ea7c44ed0b"},{"version":"11.0.1","sha1":"3b780507cb41f4b90ab486d89b43911aaf8644b1"},{"version":"11.0.2","sha1":"928509c94084a9cb3bcf380eaaf2f0c12424aa41"},{"version":"11.0.4","sha1":"5a39c7c130794342479dd9d9c073e44fdec7c918"},{"version":"11.2.0","sha1":"7df6b7f94d9414ad5aa5097ebced6bab368ce47b"},{"version":"11.2.2","sha1":"9bf6969ad4fd6d5076531e77cc0f5d29bf3dca26"},{"version":"11.4.0","sha1":"a80e03b20a87625f2adae0d660519ada5cbd9eb3"},{"version":"11.4.2","sha1":"f759644662fe4d906c92bb71a96c16c368adb9b5"},{"version":"11.6.0","sha1":"1bb1cd19d7113c8468afa9b288afb9a9245b1da9"},{"version":"11.6.2","sha1":"198b6d304c18470001debc711d0c3ba340d1a1fc"},{"version":"11.8.0","sha1":"70e7a56730175202e97af93928918101f6c3980"},{"version":"12.0.0","sha1":"b217b6a289aa61a32031115f8b14531c764f4737"},{"version":"12.0.1","sha1":"e7b1e76ba238684d09e453f5edb7ada10bfa50c5"},{"version":"15.0.0","sha1":"81a55d7240e1db696c9233159e62867d0ea6d387"},{"version":"15.0.1","sha1":"e6e8bab113fede002b3ea7096a7e920b5b56d771"},{"version":"16.0.0","sha1":"2a479e5ccfe59e4b40c6f9a4f35d020c5789b3b7"},{"version":"17.0.0","sha1":"da82f9f15497f6d36a3964c2623428840b033fe0"}]},{"package":"play-services-contextmanager","versions":[{"version":"9.2.0","sha1":"2acd895ca89b1ca3834af73ae0c2f4dea0f9f1f4"},{"version":"9.2.1","sha1":"6669c66130cd0278944912223ed5e3c98570714e"},{"version":"9.4.0","sha1":"a532f2ca0b67ec7281acc094f1b15d433f913b9b"}]},{"package":"play-services-games","versions":[{"version":"6.5.87","sha1":"c2a4dec43a87ea7b010c877b0833e0220535991a"},{"version":"7.0.0","sha1":"6ea13bb01db66f5ce9cddc463f75ec0e6b8ca966"},{"version":"7.3.0","sha1":"7633a3e2482bc83352d8dc69c5ea4887615cf265"},{"version":"7.5.0","sha1":"dd3876fb8564282767ceed971ee4174b107b80de"},{"version":"7.8.0","sha1":"7a8b1fb9276091f23621b92a180521d1fc9ce3f"},{"version":"8.1.0","sha1":"374a7fa04fd42864c54488f23498dfecef306042"},{"version":"8.3.0","sha1":"9ca6dc5b67f3fc4f6cf349087959e00fe1cceb69"},{"version":"8.4.0","sha1":"3280ae4db25853d8179b79b0ea206e7fdb008558"},{"version":"9.0.0","sha1":"ea92e5bba15b0c1844f1b9e9b24b70b2e81b4a21"},{"version":"9.0.1","sha1":"6b954a46e374563b80bb29fd7f5a01570ac03d4a"},{"version":"9.0.2","sha1":"9a6c8428b824c79c6b195797da25466f311c7670"},{"version":"9.2.0","sha1":"7d06d90c4a135575af1b1fdd42f6a180c9aadfac"},{"version":"9.2.1","sha1":"81c732a66af65ecc743780a7445f87f727c37694"},{"version":"9.4.0","sha1":"5a8ca22112fd66f25c0402db6dfa5026dfdaa82e"},{"version":"9.6.0","sha1":"344e5e285c5c4b14ed009985099d847976df104e"},{"version":"9.6.1","sha1":"34321e1db7f245ea77394ab1a804a53b0a09928d"},{"version":"9.8.0","sha1":"2033859fc9ade727f3df38aefac7eda9c70a1cf3"},{"version":"10.0.0","sha1":"35695f07d467d5bc500ca34c60c450c189833f17"},{"version":"10.0.1","sha1":"962610e256fe86154eca64f9df40dd5b13762548"},{"version":"10.2.0","sha1":"e99318774fe640deb447ec64800bf51d5382a71e"},{"version":"10.2.1","sha1":"c067f85b4e5f34a547a67b514912e3eaf42433fa"},{"version":"10.2.4","sha1":"337e900ceaea670698edf2abd249d7cbef299d2b"},{"version":"10.2.6","sha1":"f44e8a9a23d8ae0aa4224a4654175ecdb2dea2f0"},{"version":"11.0.0","sha1":"84d8b814ed8cf1c5b211109e4b76b51d53a939d3"},{"version":"11.0.1","sha1":"c1a4439dbe0f92e5b23b27c9aec3d06d7c1791ab"},{"version":"11.0.2","sha1":"41e4f44399e7e7d03dc6147df61f1e523a000723"},{"version":"11.0.4","sha1":"d8f938a26185a37e2818d8279689f4793ae426b9"},{"version":"11.2.0","sha1":"4e0a5df15ea7445dfc8f03c8fa3c23736143e529"},{"version":"11.2.2","sha1":"f6fa9c66b432e4f4cb0a3aa9545a218aff22ecc4"},{"version":"11.4.0","sha1":"4653d644a129e2c82c22c265346dad438f0e3fa9"},{"version":"11.4.2","sha1":"7527a6e9c3bde2c8edcc9c34a13b73c6848b3769"},{"version":"11.6.0","sha1":"ee0755fc832feef2e3b1a321e209a86fcae2632d"},{"version":"11.6.2","sha1":"79f22705fa2ade3e508c51e4698c6072aed11874"},{"version":"11.8.0","sha1":"dea3a59282e13970505e6b00dc51cb26015b572c"},{"version":"12.0.0","sha1":"3e3d0a29b590ec3813689fcca4c25d4d643e875b"},{"version":"12.0.1","sha1":"c03b2b3e77a9aeeca4853bd05dc752b52b2854af"},{"version":"15.0.0","sha1":"2486752bb811a9522be789e0a7b9a022eb0959af"},{"version":"15.0.1","sha1":"f1909fcd48f78575bfc215a3a8941648db78c720"},{"version":"16.0.0","sha1":"957f8d24224f4945676609435d6c9daa08b9f657"},{"version":"17.0.0","sha1":"c57bd085427e19d952eff5d43bbd275eb855a136"},{"version":"18.0.0","sha1":"3adece7e2194373faaa81fd7dea2d7da17b8f125"}]},{"package":"play-services-cast","versions":[{"version":"6.5.87","sha1":"cdf31f0abff296ff4100010ad03270025513c845"},{"version":"7.0.0","sha1":"a06804655f78aff4cea22875b6792667b8026df9"},{"version":"7.3.0","sha1":"cf6da98a7f31356452343642721197dff6f8699a"},{"version":"7.5.0","sha1":"9bde57c6590fe7fda80c5b7263c312b603fa8868"},{"version":"7.8.0","sha1":"581e276b93f893f51fc171e7319b13e3a077a958"},{"version":"8.1.0","sha1":"86def1183c5c812de791c6195630f7fcafb93f05"},{"version":"8.3.0","sha1":"9501534c2da98a16d0c5fb75cd10c5b3c44e9482"},{"version":"8.4.0","sha1":"7d1db72766c7e1a75cd18a47f6370b5850c7c6c7"},{"version":"9.0.0","sha1":"4bc25aa92ee9a0e77b6a6cb208c698ffb4d55db4"},{"version":"9.0.1","sha1":"d7a79b238d4a30c5670684ccfed0763a9d44798c"},{"version":"9.0.2","sha1":"6683ac9080c1f40823f846b018ba83c36026472c"},{"version":"9.2.0","sha1":"66e6605dadb278f67f919e2c4f3458b7721b3eb2"},{"version":"9.2.1","sha1":"27706b54f2b2c3cc46b537f8e0c53e79348744b2"},{"version":"9.4.0","sha1":"f46bd21eaf8f769e224d2554e4073e253f7282d1"},{"version":"9.6.0","sha1":"465127e34f63ea1e00f6737676a7c35358e070a9"},{"version":"9.6.1","sha1":"8f2dbefe74e0370151fa0a916dd848f0bcb4c4e5"},{"version":"9.8.0","sha1":"fd9b0924b1cdd649d61878a3ae240566ed3713e2"},{"version":"10.0.0","sha1":"da2a83fbe97b2a4a27f2cdf8a3b83b390476f389"},{"version":"10.0.1","sha1":"ef520a005ac3b00aa3ac1d29b99698500f6a4628"},{"version":"10.2.0","sha1":"e1b0e1f6607e098cf71ee4d1aab20474bdc3f05"},{"version":"10.2.1","sha1":"87adc004efb6d780e5f7ddd2f8d5a2055fd40c78"},{"version":"10.2.4","sha1":"4e010ac1e4f5f94a89694593a6e32a745c2d9677"},{"version":"10.2.6","sha1":"d75bc1bf2b511a59a93f6356e563be064e1174aa"},{"version":"11.0.0","sha1":"851a0dcd108e50dd882de61c4275e94a184c4242"},{"version":"11.0.1","sha1":"153590db8366052a4a75a751cb58da93630255"},{"version":"11.0.2","sha1":"c233b965c5c5678382b80d1648c88d5fadb64150"},{"version":"11.0.4","sha1":"6c10a8aabe94730b8d3ac4e0e67d0fe6652588c1"},{"version":"11.2.0","sha1":"a05ba02ec119ef177b1289dd8ea0310dbd370cef"},{"version":"11.2.2","sha1":"bd0e5fca36d159b0946cc7cba61f23af6c523738"},{"version":"11.4.0","sha1":"aa00ca57098ac593c650d27a7c5a8ad4abad0ae4"},{"version":"11.4.2","sha1":"b44da9ef65b1ee96225a8e4ade8cefc1442ea0ea"},{"version":"11.6.0","sha1":"71097df2c74c786b40feee0b7621640ee963661e"},{"version":"11.6.2","sha1":"554a8e260dab133b7d063877b189323c74adcc42"},{"version":"11.8.0","sha1":"72d77af83296728bc83760888b22abca621c49e4"},{"version":"12.0.0","sha1":"51e3e72d2a1ec88724d1229ae04ae10a4f8c85ea"},{"version":"12.0.1","sha1":"c35effc5f2c06ae65852ca327a31e6829034fec8"},{"version":"15.0.0","sha1":"5d84c587e7ee6a3168c7b76aee63b691d395417"},{"version":"15.0.1","sha1":"e679f5614fe045842a7daa20e5792bd06d64adaf"},{"version":"16.0.0","sha1":"4c996d4148d28fc3f25b4302f05e631d0fe094d8"},{"version":"16.0.1","sha1":"b5416774fcb54bd9dba8d8ce5997f1d391d54f41"},{"version":"16.0.2","sha1":"d8f2faf010e73726a8349437f67e8cf6e06f3c66"},{"version":"16.0.3","sha1":"97ac0cde9b9b6b13674fd074acebfdd3d4bca521"},{"version":"16.1.0","sha1":"b0c017b939987cfccc6c73b381536b7aa5af80d1"},{"version":"16.1.2","sha1":"50c6f49adbfebcad05d08220c4680592ff7b6422"},{"version":"16.2.0","sha1":"15c3397effb47bbb4a50f10e93d14af6ca92d234"},{"version":"17.0.0","sha1":"cadabcb0523872aa33dcd18229645a03c71a2d17"}]},{"package":"play-services-location","versions":[{"version":"6.5.87","sha1":"c6689ded4691a52c10e4a9111f8c5b1b965d9d9c"},{"version":"7.0.0","sha1":"bf06117883f71d9429aaf42091ccfca60839f572"},{"version":"7.3.0","sha1":"fe93fd8635b350bbf9294262ebd6b0a2cbabcb3b"},{"version":"7.5.0","sha1":"d2e3058a5408f7f381361753036954bdde1150c"},{"version":"7.8.0","sha1":"f5d3883f12cbc69b1d57ad03e420686abb6829ff"},{"version":"8.1.0","sha1":"8100c97a5870076a009f44bbd66ba68899ceb2a1"},{"version":"8.3.0","sha1":"f23ec147e1e3a379a60bd38b39b81d359c1d8013"},{"version":"8.4.0","sha1":"5b855e42f6e067321d657dacf2889c3cc4383b81"},{"version":"9.0.0","sha1":"ca5803d47e391bea31e9555e571029da50f9c91b"},{"version":"9.0.1","sha1":"ac229fc2e279f26813d2d6b284c26e0a9f8aa362"},{"version":"9.0.2","sha1":"6f15b6f32bbd569c6824c136fa251a31e0009536"},{"version":"9.2.0","sha1":"bacaaca5ca130b9fd7cfe1615c79766d8df54291"},{"version":"9.2.1","sha1":"bc21dc9a1eee1517e53dbd662498360502231fc5"},{"version":"9.4.0","sha1":"7073b325aa89044d76f6b7b33ae6c6ec81489d1f"},{"version":"9.6.0","sha1":"d9d225128108bcf76dcd99aadef1b1672b3fbf1e"},{"version":"9.6.1","sha1":"17c1fe1e5d1ddd62ce601906307413beac11074"},{"version":"9.8.0","sha1":"12a4c32351798a5b63df44060f630a39f4980c09"},{"version":"10.0.0","sha1":"95565b035c57db1d047a78e115ceba448cdb7e6"},{"version":"10.0.1","sha1":"a90fef38780a2c5b8f105d386067a3303561a29"},{"version":"10.2.0","sha1":"1a166a5cf378432c8adeb005c5a955ab8b4f90b1"},{"version":"10.2.1","sha1":"1f467d4ae34368d5e6802f19c07142b25838befb"},{"version":"10.2.4","sha1":"d6e79b1c6b4763209e687cbfaca02ee83b81c5e0"},{"version":"10.2.6","sha1":"f3397a103146fe667d1034a96ce5ecc97dca6a6a"},{"version":"11.0.0","sha1":"cadee4f31add3d85e63f35052006e04718eef931"},{"version":"11.0.1","sha1":"911aeb179389af60419636656b85339a2681f916"},{"version":"11.0.2","sha1":"8789526183b105468dce8b57f864845223eb891b"},{"version":"11.0.4","sha1":"778190090813d8d3a7d9707fe011fff3d0f55256"},{"version":"11.2.0","sha1":"be6b1b0bb97b015ff7ff6651b71dee3ed68d4dda"},{"version":"11.2.2","sha1":"91050065c8ac04478ee97103068b856fce3a417f"},{"version":"11.4.0","sha1":"6c74abc203d65167401c71d117f2e6dad9b1749"},{"version":"11.4.2","sha1":"9eeb6cfb1af40e438e4c5ce5265e6678b7fd7b52"},{"version":"11.6.0","sha1":"183e0574aa461e6d9387c0452c603afc45dbd17d"},{"version":"11.6.2","sha1":"3b72ce2e15250963f6d22a207642cefe3a741efa"},{"version":"11.8.0","sha1":"dc1d3b999387558aa4c7b4445fa3c3df6b87e91"},{"version":"12.0.0","sha1":"230d82e4c3f96faba5644c258ea155ef4f47b67a"},{"version":"12.0.1","sha1":"a085568249343a2db5071bfbfb1c8330eb00dc9f"},{"version":"15.0.0","sha1":"eb5a2432f09fa936ddfc9bcb87b128e0d3fe2616"},{"version":"15.0.1","sha1":"8ffbaabdc693bcc1cbbdfd0fa2e8881b4795d397"},{"version":"16.0.0","sha1":"3d0a3a85615ffc321394fad2e6fe5f195b89011d"},{"version":"17.0.0","sha1":"9638574a5072b3b0d40da80f6326c9f2a5ff9129"}]},{"package":"play-services-places","versions":[{"version":"9.2.0","sha1":"7983324d5c60c3c76e1c7ed94ecf5d4105a74184"},{"version":"9.2.1","sha1":"50dd7728faefbee7d10bad865b9e7eefb041f75c"},{"version":"9.4.0","sha1":"e29546d0de7e17bd98fdacbde7457baa56eab4d2"},{"version":"9.6.0","sha1":"40e693e758a5b4eaa7d58eba7c8e5034eeca96e7"},{"version":"9.6.1","sha1":"26d9004750927d32f101d630e0f944b91ae47dc7"},{"version":"9.8.0","sha1":"70e0d99f386235662643698333c32c5c6e5a7241"},{"version":"10.0.0","sha1":"822885dd64fbf9bd8b9d788b2158fcf42d82cc73"},{"version":"10.0.1","sha1":"1f728a4bb9902299ef73d78b1fe6632d250294e8"},{"version":"10.2.0","sha1":"884589b1db0593de18f1a3d4c171bd59e2b13a42"},{"version":"10.2.1","sha1":"8ee7583cc315415b2ca389e86d398d98b39816c4"},{"version":"10.2.4","sha1":"6f23496ed9cb57a1d8a8f86972a2b1cd218d4219"},{"version":"10.2.6","sha1":"ce10e53ccdbcb6108e1abc168b14d940cb148ce8"},{"version":"11.0.0","sha1":"b519edbd6fbf4ae89c94de41169505c74b098afb"},{"version":"11.0.1","sha1":"af900f20f2b5eb5fba10b1bb79e88ada8862473e"},{"version":"11.0.2","sha1":"fe6fd66422cc7ad676f2efa04d466e82423083f"},{"version":"11.0.4","sha1":"56291326703240346f491d28940fca4962983d8f"},{"version":"11.2.0","sha1":"aba01afaedb15368d9a273e856b73098de4764a2"},{"version":"11.2.2","sha1":"c584f1f96cbc90467627e59f83c4b73a5be1bbfb"},{"version":"11.4.0","sha1":"59588286dfe4c639dee261a6af9896e950f3dc04"},{"version":"11.4.2","sha1":"d9c0980ae848b756304c626648f36b4535890c4c"},{"version":"11.6.0","sha1":"8bb8b2d9c09d1bdfab45f5a17fe6ef9af0eab78a"},{"version":"11.6.2","sha1":"e1de311a60faf2ad9524eb4e9353908d4f98f488"},{"version":"11.8.0","sha1":"a9106cc0f3677379c2224a6885a8cb0945396f31"},{"version":"12.0.0","sha1":"606b460b1936fcef396ce06850ff3f35fcf9a8d"},{"version":"12.0.1","sha1":"d957ff129d84e36bb63f0dcc9946d8bbabde8bbf"},{"version":"15.0.0","sha1":"c4985f962a4c64bca4449d369ad16aeb064996e7"},{"version":"15.0.1","sha1":"18465e395ce5cae55e01c461e35839e1c2cabc31"},{"version":"16.0.0","sha1":"a594af78f4ffb0078ffac16c844df9578141455a"},{"version":"16.1.0","sha1":"85ee478847dd549462f40fee9ebaf34c6af66635"},{"version":"17.0.0","sha1":"a85e98cfc08ee949fdfc31d8ff9699dc2f2a2b43"}]},{"package":"play-services-wallet","versions":[{"version":"6.5.87","sha1":"ec69f0795da1130f5663f4d68eca3158cb6d8be1"},{"version":"7.0.0","sha1":"179515f6c5531c069219fb27945c7d410256f26c"},{"version":"7.3.0","sha1":"d0c41e0aa2e3fe05eb06f517be19ba15f8529289"},{"version":"7.5.0","sha1":"ab494a0f3d530c00e1988a6605cbf31aad4ce880"},{"version":"7.8.0","sha1":"1c8c9dbe5406f421372063a3862630f5c56a4f6e"},{"version":"8.1.0","sha1":"2a532aff2a6e9e4e33825d204fbf184248b9b97e"},{"version":"8.3.0","sha1":"42dac70194f2aa4f00d6749832509e40a96b04f1"},{"version":"8.4.0","sha1":"c38dde77cd17566b1c4ad0457e5f06f731691433"},{"version":"9.0.0","sha1":"c6514483fd0793cbc65f179cef4958f5a6ae7eaa"},{"version":"9.0.1","sha1":"f4c57e2d495376a32c091269365c1d03c229c915"},{"version":"9.0.2","sha1":"961a560bc1cd4597cd9dc69920cdf4135ebe9c04"},{"version":"9.2.0","sha1":"f7c31a926798d02c38259a2fadce95c581ccd406"},{"version":"9.2.1","sha1":"16b38bb3abed79ce9e5ff82a19521ce60abfd55f"},{"version":"9.4.0","sha1":"da41565ab6aef38a379a99758d668053a47feb54"},{"version":"9.6.0","sha1":"184c46e115e5bb73c67ddea812a7263989325661"},{"version":"9.6.1","sha1":"bc8bf12ea97c4096a83e52978837bde61d5dcd67"},{"version":"9.8.0","sha1":"b2d74313cd4e681b5be751d3b17a88ba0cb4bc1a"},{"version":"10.0.0","sha1":"e805965cb490c75fa9f0bf5f5f8a913d6a6f6d53"},{"version":"10.0.1","sha1":"4cdcceda02346ecc87cb562b07c032c2a6908548"},{"version":"10.2.0","sha1":"2e10ec87c22989913034b257c39fefbbf158d825"},{"version":"10.2.1","sha1":"98abdcb98d087d90d72877d3bb71c3b30db0f83b"},{"version":"10.2.4","sha1":"6ca44ce6f9735dce7afac3a0d2b8be1be8d5d94b"},{"version":"10.2.6","sha1":"b4466bec127b4904e28764b908f0bed9bb56fdaa"},{"version":"11.0.0","sha1":"68f1ffecef95437aceaded18683a2eb5fcab5e5c"},{"version":"11.0.1","sha1":"e03406082b65dd048ad371ae8b2a9218f1cc655f"},{"version":"11.0.2","sha1":"b9e0909b58425e9201350ae0c1345714c2aa7bb4"},{"version":"11.0.4","sha1":"b32e7a2e744f67d5626ec5ab13eee7d1c4c244e3"},{"version":"11.2.0","sha1":"fc688e9051b97606120b81abe0c90c6c956aa67f"},{"version":"11.2.2","sha1":"a64415faedf565bbfae576b98c3872220900f809"},{"version":"11.4.0","sha1":"f26b04f2972d5d2f3b18258b4cd124ad397a3038"},{"version":"11.4.2","sha1":"f1dce129d5c543937f1e5b0c36c60af6a34e6540"},{"version":"11.6.0","sha1":"49373b5b9a4013aba3c5c159a33d5391946d36d2"},{"version":"11.6.2","sha1":"3f84d883be7cc380359015aece19a73675c92c34"},{"version":"11.8.0","sha1":"e1763d0ac03ef063251181b39b6ac53ff356f4cc"},{"version":"12.0.0","sha1":"d02e6f92c2257ff4b6cda0a0af6ea7165ea36ba3"},{"version":"12.0.1","sha1":"3ba59d1d9348de42ad9d6f81b5247c0e6428f499"},{"version":"15.0.0","sha1":"7a92b47125d65eb2a31872e18fb633e3ce2f11df"},{"version":"15.0.1","sha1":"2af94a908c0c45a6193cb1a43017fc6e1e3fba7f"},{"version":"16.0.0","sha1":"f45750181d2a7106a9ab7d3c51d6e2a9e47cc33e"},{"version":"16.0.1","sha1":"ccefb9c679a4bf107668f2fc89781eb58c8c51cd"},{"version":"17.0.0","sha1":"4abfd200272742a65e40b1e7d8d8407c3523bb6e"}]},{"package":"play-services-identity","versions":[{"version":"6.5.87","sha1":"2f0dbb14df066364cc9936be024adbbcbc9e109a"},{"version":"7.0.0","sha1":"13adc4e24f87c4720793b3d2c22e5402143c880a"},{"version":"7.3.0","sha1":"6dd6ff7848102066201b436942be2b8d3381f6d4"},{"version":"7.5.0","sha1":"8fd422d32d078a7fea91560885528e691e53245c"},{"version":"7.8.0","sha1":"c619f3013fc1bffa680048316b483b2bbffc48aa"},{"version":"8.1.0","sha1":"2d64fafa70be413ef73e4fba650baedb71b8015d"},{"version":"8.3.0","sha1":"18a3c927e59bf17caaadf7abaac586094d5c55d6"},{"version":"8.4.0","sha1":"a5aa60ee4ccfa450e081085226b044330eaad256"},{"version":"9.0.0","sha1":"9e0e66646533c3e2cbc25f0d48530baa92755231"},{"version":"9.0.1","sha1":"8c757bdc9c23225b3e36c5100a27ca4b6f853309"},{"version":"9.0.2","sha1":"e66dac1f0b8b0a2e06d9857876fe38d66fbc5615"},{"version":"9.2.0","sha1":"9901bd5e829c8ca9208d6b48f34a0414fbac0c39"},{"version":"9.2.1","sha1":"1f96fc71812e63364a96b3f79f326fe485f00019"},{"version":"9.4.0","sha1":"7ce504210207836a7b39757bd5c895ac33f6054a"},{"version":"9.6.0","sha1":"8a2041d8684e02febd3d2c223d513ede71bba302"},{"version":"9.6.1","sha1":"8ac795ec65016553c1ceac281c1cae8706a3a3fe"},{"version":"9.8.0","sha1":"6f66f85585a0ae8ef7e8ab9c5021def8e324d912"},{"version":"10.0.0","sha1":"3d3be044c5b67e1b5e78b960c3810c2893e2edaa"},{"version":"10.0.1","sha1":"3a1446dc171917e6b8c9dcf1ccdda4d21c485b11"},{"version":"10.2.0","sha1":"914ade3512fbc8f452639053d1f61a9bbc5d338a"},{"version":"10.2.1","sha1":"c8d2faafed137f87bc034ac2710f577505533837"},{"version":"10.2.4","sha1":"fdf67bd0e2ca301eb4117ac540513c40b9e8a594"},{"version":"10.2.6","sha1":"f5edb2e49915820290c837e0a9013c7ad0578c40"},{"version":"11.0.0","sha1":"d522af8e4fbde15d1a76b72c5b62889da854528"},{"version":"11.0.1","sha1":"2a85d187baf2194f5a4e18162e79ea5bb58a1c00"},{"version":"11.0.2","sha1":"285ee3eed4bdc339c2035b261b28954d4e1a99d8"},{"version":"11.0.4","sha1":"369796b63ee0b82fdfd0c7bf72a9d74b9040d903"},{"version":"11.2.0","sha1":"39e24a65a5c4dc0f942c8d41a0e1b319d8cc9ded"},{"version":"11.2.2","sha1":"c456e88a3da9a64a6cee5154e4afefada231e9a"},{"version":"11.4.0","sha1":"eb09e23a4b01653f836fe36f4aafcffee9c5abd0"},{"version":"11.4.2","sha1":"533e53ab59ff8bd703d958f3711478de6b2cc9f0"},{"version":"11.6.0","sha1":"6333d0bb3117ef01b4715adb194374d88f5f3480"},{"version":"11.6.2","sha1":"e005c400803a71a7f099b7a45bed553a12f2f66d"},{"version":"11.8.0","sha1":"14004dbb13a266a98a4abac341348badd28c25ec"},{"version":"12.0.0","sha1":"4351a00ff0323b7101d0fdf0f447fcfc7b068a04"},{"version":"12.0.1","sha1":"cb1ae248b7edb3ca800f6112e7ed4494b0703915"},{"version":"15.0.0","sha1":"3eef1aa96a12d1def1b6c3d0ffaae51b5c4e84b3"},{"version":"15.0.1","sha1":"d4a4e5a9c62b9b6e17dcc86ba73950f5bc99d117"},{"version":"16.0.0","sha1":"2588015201a965ce37ca1f98bc24e444c0301527"},{"version":"17.0.0","sha1":"4cd8477a6c9144c68300fa2d6b856ccd909748b6"}]},{"package":"play-services-analytics","versions":[{"version":"7.0.0","sha1":"7daaef323aa30f00c93b4ab90da5ab3acf6190ce"},{"version":"7.3.0","sha1":"f300f7cfa06631d1b950f0e37c04c31bb7a5cd60"},{"version":"7.5.0","sha1":"f6617204a6a86ee3699cf90bd4a97af5dc8a59e"},{"version":"7.8.0","sha1":"664f5281f142ab70659245c9d84a3884ceb235ac"},{"version":"8.1.0","sha1":"8b561248a59d8b2a7ff600a5cd2852f7787d00f1"},{"version":"8.3.0","sha1":"22ca8a1294f266a9fa61db0fcad5724efc7a99b4"},{"version":"8.4.0","sha1":"b21b9d339643faaf422e6907ed23174c807eec75"},{"version":"9.0.0","sha1":"2c55c24cadcbbe903ac6e894dc8bf28ac7cb341"},{"version":"9.0.1","sha1":"369c8a8d419b4a79d87c4c5fd980a5f342aa810e"},{"version":"9.0.2","sha1":"3ee2ab06cfffebde5d3c1ebf114e368573902e29"},{"version":"9.2.0","sha1":"1ca438f12e3a1f4570c541f466aaf6128b49cf22"},{"version":"9.2.1","sha1":"a1db2dfcf15246200f1afd609d49a925f14889ba"},{"version":"9.4.0","sha1":"525483d685be074eb54a5f7b1690dc191415620f"},{"version":"9.6.0","sha1":"4872dcc7aed67d1f3e6d28d49367467c3cbef26c"},{"version":"9.6.1","sha1":"d58aa86bebd7905bd7e200a926539c4c4344a6d0"},{"version":"9.8.0","sha1":"2cf9b23f1b4606b83398f8623f49796af1098167"},{"version":"10.0.0","sha1":"263c91d3d59b0e4f28d81a03d74d2d264266a3d2"},{"version":"10.0.1","sha1":"2e6b42253275c2b6a8c8aecaa5f448b4a87a2f5"},{"version":"10.2.0","sha1":"b04256c624143b09cfe385145850d392ae3990ca"},{"version":"10.2.1","sha1":"32257d63069f451769d5b4203cb05da86f0fc517"},{"version":"10.2.4","sha1":"7c9388b09e927893985bb3446f0084e331ed2787"},{"version":"10.2.6","sha1":"2b3f09ed6165583da13dc2268e3b83980b03c082"},{"version":"11.0.0","sha1":"4e4797d0d5d3783b322a53ef34c0ee5859f8b8db"},{"version":"11.0.1","sha1":"3cbab0d86022d14f605f545581fde86ee9796bb0"},{"version":"11.0.2","sha1":"477ace4dc6eb93a56023322125cc1c7d8d7228d"},{"version":"11.0.4","sha1":"da2c20d7dc73e405aff6e6304b193bd338e239f"},{"version":"11.2.0","sha1":"46d19652a6cb0636387b1cebb3b2e330ccd85c4a"},{"version":"11.2.2","sha1":"6c9b9936ee43fd7641bd5942f4370156e039fb80"},{"version":"11.4.0","sha1":"59701aeab6e84fa4549cb0d64cfae7f84778ab48"},{"version":"11.4.2","sha1":"52dae5731b8a23d6c8fd0042f181ed0322ed0c48"},{"version":"11.6.0","sha1":"f3332d68a927770e30f8b2922076989cf59b89c9"},{"version":"11.6.2","sha1":"c921166e2823daf519fb2c79a323355621666eb"},{"version":"11.8.0","sha1":"b2ad200616cc49044feaf8c70a34ce1c60cd80b9"},{"version":"12.0.0","sha1":"3ab3501d86347b62e4c234b0e74e7cf75435fd23"},{"version":"12.0.1","sha1":"cf31af16ba72ae7bcf215afa92c0662dfce60552"},{"version":"15.0.0","sha1":"9055dace288197a05249d7e71900d68e9515a2a0"},{"version":"15.0.2","sha1":"6892f0ec89b43d66615171cd5b848fd6e5acb79d"},{"version":"16.0.0","sha1":"421c2aaf4843cd9d3b490ecc8d45c57a5c181ac1"},{"version":"16.0.1","sha1":"80f178e77f19f041dc05a26b6beb6d171a9c401b"},{"version":"16.0.3","sha1":"902db665fccf72e094f06cd4ac77bce4dfaa261b"},{"version":"16.0.4","sha1":"bd2b039f6db0fe7f54f753f49a9f33d57ea1b1c0"},{"version":"16.0.5","sha1":"d97020080fae02550da42fd9bc446aa9133e6d3d"},{"version":"16.0.6","sha1":"bf68ec933be675662ade0dc9daa77cf1279a599c"},{"version":"16.0.7","sha1":"a09c74ff159c9ad593a92ed00fb881a43bf677cc"},{"version":"16.0.8","sha1":"b6f42d4fa2d5af2167ee717539b33ca36a049d94"},{"version":"17.0.0","sha1":"db4c075f7739c69eca0279012add198759dac372"}]},{"package":"play-services-appinvite","versions":[{"version":"7.3.0","sha1":"14932a1c5be9648aa4dc920c427e44c588c162a5"},{"version":"7.5.0","sha1":"19aaddb67f1e37abea5e421e66ef0f281eb33c3"},{"version":"7.8.0","sha1":"1134975d55a8b0dce282f78c88956aed85b827cd"},{"version":"8.1.0","sha1":"9e1b41335abb4f426f74960760f3b955b336ea97"},{"version":"8.3.0","sha1":"83d13cc82c96a0816fb99345cf180c5aa75df1d0"},{"version":"8.4.0","sha1":"8309c99b270176b1d2ac1df7d2bd9b8dc28b4a3a"},{"version":"9.0.0","sha1":"b6a70416ecccb7fffbfe515dc05758335124d0b5"},{"version":"9.0.1","sha1":"96bc104c33ea8b4b5c35f22f20a74cd578e393b8"},{"version":"9.0.2","sha1":"d128036f55d56c08f9c652164fd0b50213e84bd8"},{"version":"9.2.0","sha1":"25dae1d856e80838dfd3895baaffc7e49517699a"},{"version":"9.2.1","sha1":"5de6b141c0e92c0a0742208343690df4f68123f5"},{"version":"9.4.0","sha1":"25938ebd3030d9e050dcdb06fcb67a988ce88a52"},{"version":"9.6.0","sha1":"4ccea8428b5134b66a8094d21c23c9cc9e147a68"},{"version":"9.6.1","sha1":"234d5a24bbc96d828be43196a700c56e612a319f"},{"version":"9.8.0","sha1":"a0cba0a2f6e51d391fde5829557fa546be30f00a"},{"version":"10.0.0","sha1":"6f92d0552df07128f608faa4735108bc573d1ce6"},{"version":"10.0.1","sha1":"d10a231fe9af7c01a7bb96426e9c109d3695bb06"},{"version":"10.2.0","sha1":"ea38ee09f114cd26a674ff9fd8164b1bcdc39a04"},{"version":"10.2.1","sha1":"ddb1b6f47edec82c1ce2f04fc5d5b7c1bbdcf765"},{"version":"10.2.4","sha1":"53c91d73f2d9345b371793c7edfcbddda6232995"},{"version":"10.2.6","sha1":"c4b881ff9bae6efb23d8258a46008b86feba0d73"},{"version":"11.0.0","sha1":"96a7422d8c803583ad4884d87a966380930f449a"},{"version":"11.0.1","sha1":"892c0536aa69adba45cff12049fcf788955f9839"},{"version":"11.0.2","sha1":"52ca2c09a38183b24ac506b19daa7fd2353636fc"},{"version":"11.0.4","sha1":"295f12e661526a27eac4ec4ad3ffe9324244416d"},{"version":"11.2.0","sha1":"84ff0e576f17b0d510c41a6819fbbb035207179f"},{"version":"11.2.2","sha1":"6e56f559dc1be8cb1fb35ca84807d0629d699ec7"},{"version":"11.4.0","sha1":"546ab7bbfbeef97a18534f53f7e11055135cf09f"},{"version":"11.4.2","sha1":"1913296aeb0e2f099e66e60eb074bab65387572e"},{"version":"11.6.0","sha1":"b1cee6251e444e39faf55daaa818ff6351e0d135"},{"version":"11.6.2","sha1":"a9cd5374f9fba4bfe9f172303edf01dd050b139"},{"version":"11.8.0","sha1":"fbb4313a36ab1a163ccc43ffbd46bc8174bd0cd1"},{"version":"12.0.0","sha1":"4a6e35d8a8ea4420fe840030a841049cabd95a97"},{"version":"12.0.1","sha1":"79a7be09a23b1e12d9efe453c85a7325f3bc2227"},{"version":"15.0.0","sha1":"94cdc349920adda0a4597211517ed0fa832893ec"},{"version":"15.0.2","sha1":"b79bd01b44588f69e918123d88a5efea899b5f36"},{"version":"16.0.0","sha1":"13984102901e414a99ff3bf3300f73bfe326fbbd"},{"version":"16.0.1","sha1":"d5bd96ae0dec9d09854dfe384deba54e46006275"},{"version":"16.0.3","sha1":"8bf2b4459df34b700de7517608105c34d07927f3"},{"version":"16.0.4","sha1":"dca50ece77235170f12e35a387defc68ba73907"},{"version":"16.0.5","sha1":"4601905a99f036d77becabe3e56d4b84e8238e09"},{"version":"16.0.7","sha1":"1b773e47282096f1d55d68dd1e7fe00bea7e8801"},{"version":"16.1.0","sha1":"2752282549967dfe5cbf460e739fb848814e652f"},{"version":"16.1.1","sha1":"e38c80e3cb3ad244dd2a85e9375a20ed72dbe9d1"},{"version":"17.0.0","sha1":"41f07729ff06c49b736cea7220c3c33f00a36e85"},{"version":"18.0.0","sha1":"ac1351e8391366c7da47d757037ad3ccfbfe4f26"}]},{"package":"play-services-auth-base","versions":[{"version":"9.0.0","sha1":"630b57d82ee09875a1bc00204713a5ce0db5be45"},{"version":"9.0.1","sha1":"1583ec71785bdd483b5df4a396efcd75199912f3"},{"version":"9.0.2","sha1":"2cdd629df705ff7f1ff98cb83b681ff845be7154"},{"version":"9.2.0","sha1":"9cf88bb5c459e9a4a88f527b491c4994a12f70f"},{"version":"9.2.1","sha1":"ece2672d0a194571462ea7153307d5c23a42142b"},{"version":"9.4.0","sha1":"aca37724d07f2299fa6b19fd5e5c76fb0d47d920"},{"version":"9.6.0","sha1":"e8a029f9db158b64a03e5ebcea4712c2ba149f90"},{"version":"9.6.1","sha1":"cfb650073c1ae376438d97dbc26c7143da7ac457"},{"version":"9.8.0","sha1":"3234eee6de75c8112acf6aa3bba62634f7370abe"},{"version":"10.0.0","sha1":"6c9fb5086d781a9ec0a69460a12282388b4ccb19"},{"version":"10.0.1","sha1":"d46159cf8b7ffa6f9cdf1ba4bd98f50c7cbfe9dd"},{"version":"10.2.0","sha1":"3828bc85cead8d6ea9b47023231cac88eff3a780"},{"version":"10.2.1","sha1":"831d68b5e13ed64c28c6b72baa0b3c1192a2744f"},{"version":"10.2.4","sha1":"43387b4ca214b41da6b50c651d7bd9af3e0d2186"},{"version":"10.2.6","sha1":"1f64326e3f4392c8792d0adee68d6c5fdb4062ed"},{"version":"11.0.0","sha1":"7efe3c8e47dd1a6c944255dde01a89b0f614b302"},{"version":"11.0.1","sha1":"8553ef8ff1f74bdcc17dab22ec03d46343536acb"},{"version":"11.0.2","sha1":"b47c392e976c498ba11c45adefb24c607fc45d3f"},{"version":"11.0.4","sha1":"6b17e80fb47a8fd3ff21d792a4f4df695cc00798"},{"version":"11.2.0","sha1":"536a43fc1a6c71b0af29ae5c899c780d6ee2e7db"},{"version":"11.2.2","sha1":"9bf91ea2c072137831c08f2eb9622b780a8361b1"},{"version":"11.4.0","sha1":"322dafaf156742157d04a147c0968a67149ae6cc"},{"version":"11.4.2","sha1":"9ec47e76d8682a6108ff13fc17c84803fec99023"},{"version":"11.6.0","sha1":"3b9cf3d368a2794d8b631fb7d1ece5f412fefee8"},{"version":"11.6.2","sha1":"1e26c04e14c1cef3ff4b0c6d0db13364741b8df0"},{"version":"11.8.0","sha1":"c8657cbddf66bd7962ca6bb6e5c38b743220c234"},{"version":"12.0.0","sha1":"9380cad89a76b7b0039bb0d363ee19b4d12c2c49"},{"version":"12.0.1","sha1":"487ad326b28d86506e518a3f0c7ed88b06fab6d0"},{"version":"15.0.0","sha1":"98f7566e19c999db61a95ca612235b9f1ef6901"},{"version":"15.0.1","sha1":"5bd8e70732f78055481da84b86d831dab9dde0a6"},{"version":"15.1.0","sha1":"375d211c21c52955ad372a782cbe0acf18b92976"},{"version":"16.0.0","sha1":"6691446e870b1fa8e5bb0a8742aa00d129d68b18"},{"version":"17.0.0","sha1":"8f69f3634fdd1d2510edbec33de4c52f6472ed66"}]},{"package":"play-services-auth-api-phone","versions":[{"version":"11.0.0","sha1":"a7dc4b149b9a38abe4b3c0b3dfc6291476bd30ad"},{"version":"11.0.1","sha1":"a3437e9dd8de86a4609b1b5077e012d29319473f"},{"version":"11.0.2","sha1":"a7700a42c72c49945b21ca2405a23bda4fd8c9bc"},{"version":"11.0.4","sha1":"a8178036e036702b205ead42137631813db83976"},{"version":"11.2.0","sha1":"51571b6486e021b5b7e4fa02e6aa525ba05b4025"},{"version":"11.2.2","sha1":"eaf2457b46f8df3577443977fbf1eb394163ae6c"},{"version":"11.4.0","sha1":"b4650369992c08bf9f148c1edbd4c095a4efef07"},{"version":"11.4.2","sha1":"f0428d4981b91594e37789ea0ab49c372eea75ab"},{"version":"11.6.0","sha1":"ea0cd4803b38644c74994364ba44203cc73ed8d3"},{"version":"11.6.2","sha1":"d1fb1141644cc521457980d180f02d8a612923b3"},{"version":"11.8.0","sha1":"86f6ad4c6f6906f72937b937af90b8f55dea6164"},{"version":"12.0.0","sha1":"161d0075e9452133fbea5e8c529fbc3af1cd13f2"},{"version":"12.0.1","sha1":"53b1771f827032aa1dc7a9335717555de81c7bb7"},{"version":"15.0.0","sha1":"22def7cb703174ff7ea42172f113112a7fbcaa01"},{"version":"15.0.1","sha1":"b278d574d9fbf5c88b13bff09c00d5ce0c2c0c69"},{"version":"16.0.0","sha1":"4d8ec5e86a7b76cc4a8ba0edca6df183e17b8761"},{"version":"17.0.0","sha1":"4abc3c5e0095eb657d2d04fe5fb507a8b183c658"},{"version":"17.1.0","sha1":"5f22ddd1c248289241511814f71402a09acd32ad"}]},{"package":"play-services-gass","versions":[{"version":"9.2.0","sha1":"287956540a535a0f0db7cc372f162b98c97a0fc2"},{"version":"9.2.1","sha1":"ed913193fdef9299acd9978febadee8422bd2795"},{"version":"9.4.0","sha1":"6860bfb3c614e5257da2e0ef7209911870f643c3"},{"version":"9.6.0","sha1":"2f31dfaac9f2deb8bf1e46370c6c1e7e36576db5"},{"version":"9.6.1","sha1":"a60b6aed9c4e422bf1d4cfd2e9a9240055a1ba06"},{"version":"9.8.0","sha1":"9f55aa5c38b05ba3326115e3c684ce4e21168314"},{"version":"10.0.0","sha1":"1238396598f0fdea456cf0fc388542edbbcd0f14"},{"version":"10.0.1","sha1":"9674f93c79d194769a53be4504ad97a5040e7515"},{"version":"10.2.0","sha1":"f24ec5e4bf925a08b0df15b2322720f7baefe246"},{"version":"10.2.1","sha1":"7ca144e19310bc1247a603f0f7d7e37935a7a4e1"},{"version":"10.2.4","sha1":"dab86586d7e3f291175e92c16f13c3f1d56929a3"},{"version":"10.2.6","sha1":"e7f2c91228e2852b61628e57f99b368e9a13759"},{"version":"11.0.0","sha1":"e19b6a1c1cf0d46755abf951b7080ec1d79b6937"},{"version":"11.0.1","sha1":"7f9fa5780543ec9bc3789205801d2c8cdbb56b7a"},{"version":"11.0.2","sha1":"deb4d65eb17887b9f53b939119dee741ff1db314"},{"version":"11.0.4","sha1":"99bb2f6d1087b7bcb3392940fa350bb9c6fea4b1"},{"version":"11.2.0","sha1":"19022f3dcbf276b551fcdcd1afff75284d753abe"},{"version":"11.2.2","sha1":"f17cb7d2dd10edee39227a59d666d05a8db2b08b"},{"version":"11.4.0","sha1":"59890f4d40596847719911414ce450284c0e875d"},{"version":"11.4.2","sha1":"a1648df5040c19edaac7a888484e266866537f6e"},{"version":"11.6.0","sha1":"9c3054cb5a85d3f6e615e4cc902a2c3b3c5b882e"},{"version":"11.6.2","sha1":"2f65e49eae1f222dc087fa54c94e60bbbdbcd7ec"},{"version":"11.8.0","sha1":"d87d3e1a9e9835ee4d9d40ed5762f735e33d935a"},{"version":"12.0.0","sha1":"5bb494bdec18ea82d42862c259404692e585e9b"},{"version":"12.0.1","sha1":"ab266b10e20f0aabe2537cb869945b0ea767e3cf"},{"version":"15.0.0","sha1":"77cc352f7e12226b331556ec48eead954cc68c1b"},{"version":"15.0.1","sha1":"4047bbac5b991c8871a1d846a095d69043713250"},{"version":"16.0.0","sha1":"6f6ec01ff7479c44db2e95068105f3d94aa22ca2"},{"version":"17.0.0","sha1":"fd7f5bff64730c8a6f9cf43e626a2842d92e1dd9"},{"version":"17.1.0","sha1":"6627f9c0d39e7f5382ec120d1959dd5c00509095"},{"version":"17.1.1","sha1":"17ab4fc1f54cd0284ec884b75c8c36eb861bd45b"},{"version":"17.1.2","sha1":"2c5f3df3c6ab4d737634e20ee922f779aaacfef0"},{"version":"17.1.3","sha1":"c1132bcd66165a08b654dc611f43f9f6e9482eb"},{"version":"17.2.0","sha1":"e4286f3dcbd6a9aa247dcd87d273c4ee57d6d87f"},{"version":"17.2.1","sha1":"7f5613104c01ad6a3da8ed2009219c514075f66a"},{"version":"18.0.0","sha1":"d46d386b8f0e8704dda2f2e39a676bc75e372412"},{"version":"18.1.0","sha1":"e647e653efb3aa4b734659227c9a0fa12bfed774"}]},{"package":"play-services-appstate","versions":[{"version":"6.5.87","sha1":"41fe77db645d72db47cd2b2aa45749bb0ccb0078"},{"version":"7.0.0","sha1":"a42ccb7656065c55d044fc66b50992884abef27d"},{"version":"7.3.0","sha1":"2edabf07bfbc222423f541ad025ac9092dfc91e"},{"version":"7.5.0","sha1":"28dcbd5184b09eaf365a2d86ae8db8c99da01726"},{"version":"7.8.0","sha1":"90796ec6dea7bdfdecb71a7546576fa2fed02f32"},{"version":"8.1.0","sha1":"3743a2df988bc236e1d13ba5faf18c7db1235584"},{"version":"8.3.0","sha1":"a6e3ed45b64f1b0965068e2a88adfc76ebd97b9e"},{"version":"8.4.0","sha1":"bce3a500ad3034e90c8295356693317fb9e36cee"}]},{"package":"play-services-fitness","versions":[{"version":"6.5.87","sha1":"52d6d1a815e6c584a2c6796e49e6ceef9e4de49b"},{"version":"7.0.0","sha1":"3d5e09301674c16a07dc016a76123c0ae4b6787a"},{"version":"7.3.0","sha1":"6068fab27741ee9211c4943f68e355581f3131e8"},{"version":"7.5.0","sha1":"9355b6ad6d02bf017bdee8b3419fbed835d832f2"},{"version":"7.8.0","sha1":"9a936e300a08263f753b636a4dbe806196247585"},{"version":"8.1.0","sha1":"fa37f22e08f9a07d7663a3d77060b6c56f642ee5"},{"version":"8.3.0","sha1":"15a6a544e25015ad64a9065ea503ceb65e09906f"},{"version":"8.4.0","sha1":"45c195aeb1bde8cdf52d512468e172a44ba999e8"},{"version":"9.0.0","sha1":"8a1372c2c1dcc85c8aa45f1c439d283901aec509"},{"version":"9.0.1","sha1":"e7bff01fbceb16183c7013b456cda6e10fc5bdb"},{"version":"9.0.2","sha1":"9656d3466d7672adcc951b0b5eeae40b33539001"},{"version":"9.2.0","sha1":"9d9a0b981f2f363d5ab71107bcad4c9bead93b65"},{"version":"9.2.1","sha1":"db211e368b63626f4827b2bcb22dfec119c2b9fd"},{"version":"9.4.0","sha1":"2772b16f4eb8412728a5111e70990536ef156d29"},{"version":"9.6.0","sha1":"33d8be07659d852170314301e858fa6d5a2142df"},{"version":"9.6.1","sha1":"b8ac74b96f74afd678b425a35a95e49de6625da0"},{"version":"9.8.0","sha1":"2fdddd94d9e8f05825a54a798e30e3e232524bb3"},{"version":"10.0.0","sha1":"af5b6480e0e2f43c8e41f19ce38fdfc74afe3d00"},{"version":"10.0.1","sha1":"c10eba045a2a3f7fd08cd9fd8f640f776f80e437"},{"version":"10.2.0","sha1":"317b33dfe09e3362492e076e3413ca2a39826cd8"},{"version":"10.2.1","sha1":"a7c12e5b1bc84726eb5f2de5be1bcc9ab71b7fd2"},{"version":"10.2.4","sha1":"f3333e20a518a86f963b264188fe9b40bd4d39bc"},{"version":"10.2.6","sha1":"e46361b6b90f3b3b8aa0008e21a07bfb35b0305"},{"version":"11.0.0","sha1":"1d17bbdfc18f9f5680459d6b44652b2dc941426a"},{"version":"11.0.1","sha1":"e0e08d7ba410a2d0e8e4f097ceef1063a0c01f34"},{"version":"11.0.2","sha1":"389a12896ed26aa945a9ec7ae07ecc758c76f687"},{"version":"11.0.4","sha1":"23c336fbad848b34862c9b2a7dfb0ee9cb7eddc6"},{"version":"11.2.0","sha1":"489cb38f0f0714c19da040343a62ead23c3c6ea5"},{"version":"11.2.2","sha1":"bbb7f54f22c4557312cf50f3f9fcd94d9c8944ed"},{"version":"11.4.0","sha1":"e4512b98467c171dfac6bf3e9ebc8cc3ea9222f4"},{"version":"11.4.2","sha1":"a164944db905e0373f5b34e0bfb8ffcfb11a6143"},{"version":"11.6.0","sha1":"c263a86e2a07ca5b4ded1982c70c505ebd3ee709"},{"version":"11.6.2","sha1":"87b7ddcdb393f4d3d94171bc6b2dc68f344cf5bf"},{"version":"11.8.0","sha1":"dbd9dff7555302cd40a21ee35f7143f0ced67787"},{"version":"12.0.0","sha1":"6d9ba3c0adbb3c17b3cfbaf21f4eaf705cddf3d6"},{"version":"12.0.1","sha1":"515978722b2d67ec3dacdcae25f9ffb34867bbe8"},{"version":"15.0.0","sha1":"e01d8926bae13cf27c4c1e793b16883dd8c1d69f"},{"version":"15.0.1","sha1":"9739af10a5dc551e81cd7897cea7a1bb17b70a12"},{"version":"16.0.0","sha1":"64ebea20f9bf6e694df6b0a0f98247a71cb3da07"},{"version":"16.0.1","sha1":"9002a24942e821454275da4a070a9821c07e5e0c"},{"version":"17.0.0","sha1":"5005faf35b39c421d8dffed82ff4eba241f3e083"}]},{"package":"play-services-drive","versions":[{"version":"6.5.87","sha1":"b59c053ddb7d9744a11b3418379dd34ce00f36d9"},{"version":"7.0.0","sha1":"ce632501b3099947957f440ff84f140d2adcb4e3"},{"version":"7.3.0","sha1":"bcde8ef3a4b2027aded8682427f36901b14e890a"},{"version":"7.5.0","sha1":"fc573281089763f262e6300ef4240d12986ae9de"},{"version":"7.8.0","sha1":"a6a384d01e5eb4813211193b47cd7c5e25150212"},{"version":"8.1.0","sha1":"dca66ef40143b6ca27ada9fdccb6a736b2bd3d25"},{"version":"8.3.0","sha1":"9daffa66af250a07d63878146c07007581d6528a"},{"version":"8.4.0","sha1":"fda8ff74dbc857d0c5ff44dd09c334ae5d1b5f69"},{"version":"9.0.0","sha1":"d26b2159753381f2a147957b0066d0163a4126f"},{"version":"9.0.1","sha1":"37b4586ac4accf20ad6120f40ea17595094dc1b0"},{"version":"9.0.2","sha1":"95bf2a5a932fe872ce2fa16ebe86f67a8abb505a"},{"version":"9.2.0","sha1":"d466ae583f28f092ac6048f4fca7fbaaa19a4ba2"},{"version":"9.2.1","sha1":"7000fc1f16ba91a7c896d46c051f42d9db591c69"},{"version":"9.4.0","sha1":"f4fc4735f4550f90f7468a71753f4c2db7fe5d6f"},{"version":"9.6.0","sha1":"5fecf1d06f4a591e8f94fe82f023b40479864f99"},{"version":"9.6.1","sha1":"61b817ebcd1103831653fc96775eb3ea292caeb2"},{"version":"9.8.0","sha1":"5ccddc7be43e4480c7d08f3707619fee2a08bcd2"},{"version":"10.0.0","sha1":"e2b5d8cda9742825a8d158dbae1bff73691011f3"},{"version":"10.0.1","sha1":"f0e88845ce41d5d7d04a98fb228d1b38d61ce4f1"},{"version":"10.2.0","sha1":"49e4091ab0a2222a592e5b4709b23c57778c7495"},{"version":"10.2.1","sha1":"9bbd9d228cd9eeb57298e1ce19975c25e0a19b8c"},{"version":"10.2.4","sha1":"e5c2f349ad131055186156b2aa8ac1f0e3e4d3d8"},{"version":"10.2.6","sha1":"47d10ac8013c6acb9f64d0ef8e50dc47f8da0d88"},{"version":"11.0.0","sha1":"8a27ee81e3c6f1e56fb92bbff16be64d3ce91daa"},{"version":"11.0.1","sha1":"79e455084bf41474c1464375ed1f32a34d1d1b51"},{"version":"11.0.2","sha1":"a90fb3cfd3e96b4ab897cea38f5b6ddc98908416"},{"version":"11.0.4","sha1":"26d62981a78b90f146c207417da09599546cbcac"},{"version":"11.2.0","sha1":"e3320e07c045671990208127a301a61033613baf"},{"version":"11.2.2","sha1":"c7e6cc9173b3d364e7d3b8f3d5accab6a5413f0c"},{"version":"11.4.0","sha1":"d0e6c43f0f634c7318625006ad3affde1494ebdf"},{"version":"11.4.2","sha1":"2c0b4e83f23ea8d7bc3eb7e1f637dd2f6e972ca6"},{"version":"11.6.0","sha1":"a8ee811281db3950272722439324c51400ab8c41"},{"version":"11.6.2","sha1":"c8172f7e894c64b905e4a45ce2dba2c43f1264d6"},{"version":"11.8.0","sha1":"baae8bf1f8c7e9df9fe7af240a4c9f5f32ccbd8d"},{"version":"12.0.0","sha1":"a9d49385a8f074a769e990b1e0cf8c29809cfbf2"},{"version":"12.0.1","sha1":"4b38b3c205020d9de40604a0629b5d4f15afa030"},{"version":"15.0.0","sha1":"5315e4c84333449d9a5a50830c7a6836c1f362d4"},{"version":"15.0.1","sha1":"2f40c6badcc976d69caa77ce3ffbcdbd0d67266b"},{"version":"16.0.0","sha1":"62b88d720bd31a1d8cd5c42d513576c3d5c19ec0"},{"version":"16.1.0","sha1":"782c21eab5ba4ed6eef248ba32fde246546e877e"},{"version":"17.0.0","sha1":"a86bf7747b7ddbd519b85d62d04a221bd7bc9787"}]},{"package":"play-services-measurement","versions":[{"version":"8.1.0","sha1":"eda8b5a3263b07de899b7657583030f631ce1022"},{"version":"8.3.0","sha1":"cd747cd1587699a572c2cf6ab758f7888c90f907"},{"version":"8.4.0","sha1":"aa41e12a05db7ca8903973ebf31754e5b8fbf68a"},{"version":"16.4.0","sha1":"d5a5b45e252557ea8041a26dc4645d32c12f896c"},{"version":"16.5.0","sha1":"761764e3f6a6df71dbcbc0b602a87bdd12590a3a"},{"version":"17.0.0","sha1":"42ec868f2eae92c1047eee8a59c51b26c695023c"}]},{"package":"play-services-ads","versions":[{"version":"6.5.87","sha1":"c88636b6bfd6a88404b597034ff0ec61319bc8bf"},{"version":"7.0.0","sha1":"40144c7c0dd83a275f1f4ba088c20dc4a06d2e4b"},{"version":"7.3.0","sha1":"b01da117d4c5a0fb21e06b673d7847ba160520aa"},{"version":"7.5.0","sha1":"fb20fff2a7c08705122fc1f1d406c801609b7192"},{"version":"7.8.0","sha1":"4617156d93d749231fd150af4baf9572f23938b6"},{"version":"8.1.0","sha1":"a6d05d53783139b049cac1ba7726d62cc5cbfce4"},{"version":"8.3.0","sha1":"791d7649600726898b12be950e36468641acb77a"},{"version":"8.4.0","sha1":"5ca80130c2aad631de10d911f17055fcf76a6da8"},{"version":"9.0.0","sha1":"72eb53b692bbd0a330781ccb1f07ec4010663b3a"},{"version":"9.0.1","sha1":"b1b53ba347d6d1371e01d3109293e6aa80b183c8"},{"version":"9.0.2","sha1":"e94986c7ca04ad6ee6add67aa5386908b1512f1f"},{"version":"9.2.0","sha1":"5a3b7e4f88a9d91814004cedaa84707f3a6b8cbe"},{"version":"9.2.1","sha1":"2c3e523505818150e29bd08fd21cb6591b66272d"},{"version":"9.4.0","sha1":"ab24ce1af7ea4bef29c0ed4101ffbd45cda31d1e"},{"version":"9.6.0","sha1":"5a51f5021cf6dd70ac46abaeb348033729f5ed8d"},{"version":"9.6.1","sha1":"eef07b8a038faed348f9cc7219f6d614aa1e6a9b"},{"version":"9.8.0","sha1":"9181ccd06bcbcd8bf5eb4b5e419cac2e86ddc72"},{"version":"10.0.0","sha1":"376f370f5267d13aa1199c75944ab3951d3ec026"},{"version":"10.0.1","sha1":"fdc21feb59d0dfff09b440a09e2262f57c694bb8"},{"version":"10.2.0","sha1":"bcf3e28b1585f05dd71b6bf0f8da8fbaaace068c"},{"version":"10.2.1","sha1":"56a980e05af335a32a05564d1d6fdd8c4081cf1e"},{"version":"10.2.4","sha1":"29cda4a554a3235673032528b787bd6a0e57548f"},{"version":"10.2.6","sha1":"42d707a597f0d48178dd7893bdfd8aadfa394a75"},{"version":"11.0.0","sha1":"b00dfc0a019eaa051ad3e92c7930d4d73b6f76b9"},{"version":"11.0.1","sha1":"8cc1322e38d65b6c71f03b9721c82c26afa08ba5"},{"version":"11.0.2","sha1":"e2f8f948cab93441bdf6950dd460d88f59cfd4f0"},{"version":"11.0.4","sha1":"56510c33d038ae25036a678a8b75f2c323d3d4f1"},{"version":"11.2.0","sha1":"2e7fa7eaf374c62534f0a1c4617ce12995b8617a"},{"version":"11.2.2","sha1":"99eb432a0a3d7348c455e95196301e1e9d626dfe"},{"version":"11.4.0","sha1":"47e18015495117b30927ba1a3b98ea1a3ed31792"},{"version":"11.4.2","sha1":"3cc82b274cd9bea2daf53782f3db22d2022b1fbb"},{"version":"11.6.0","sha1":"e4a1e9a996d3c5031696a1189346523962093d54"},{"version":"11.6.2","sha1":"cf7b939feec119ea3346ef4e4d3b037023a65016"},{"version":"11.8.0","sha1":"2985b09e215ecf30cf6695f73838bdeb0135749a"},{"version":"12.0.0","sha1":"810a49a3d42072e103e19a2fe81214fef28b2f43"},{"version":"12.0.1","sha1":"c243dcc9aae233edf39955cd6c276d3d653afedd"},{"version":"15.0.0","sha1":"603df767d87ca85acd5bca610a4feb6e3fd88759"},{"version":"15.0.1","sha1":"9ad9c3c8af0163f8690ece434f6d4023bc15eadc"},{"version":"16.0.0","sha1":"9e4e1b04a6357817afd2004a3547d0e43e8135cb"},{"version":"17.0.0","sha1":"32699ca916280a985d7cd321f1f221bf40649cf0"},{"version":"17.1.0","sha1":"6e6b6a4e74e0661e1a7cf0d55b99d0c1a363c159"},{"version":"17.1.1","sha1":"9d1e1e4137ca8b16e3fe85c925c2520a4837e9b3"},{"version":"17.1.2","sha1":"facb0a29b3f1e43e81109253167c76f63715beb4"},{"version":"17.1.3","sha1":"681c51dfbbff29a9af729b6d638699897c96e2c4"},{"version":"17.2.0","sha1":"f3c8479a8a954d58a6203df154683f674c1b4fd9"},{"version":"17.2.1","sha1":"621a990dce7f0f081fae1a086d3df3910384333b"},{"version":"18.0.0","sha1":"2b1236a7b2bb1c319d665650efe685df843082c3"},{"version":"18.1.0","sha1":"e079d0a6d8ac15224a433cd2f1d3c7d2e68dc49c"}]},{"package":"play-services-clearcut","versions":[{"version":"9.2.0","sha1":"2172c7f82654a1bfbfab4775ff3041fde00c3c62"},{"version":"9.2.1","sha1":"fab93b7c07b5c91bea2be9f63892bb392862662a"},{"version":"9.4.0","sha1":"a858991936d73825619b2b02c489bd574910012c"},{"version":"9.6.0","sha1":"a27cf873d058599598cb98d828267427f22a0f6a"},{"version":"9.6.1","sha1":"d2b27cce56b336553a4e9887809c4d47d14f305d"},{"version":"9.8.0","sha1":"c76ae0916293538bfb6a14fa09da4d395da4d81f"},{"version":"10.0.0","sha1":"81030644803e949de810796b7600da89f0c51b21"},{"version":"10.0.1","sha1":"1bcd5f70848107615c9a8f1a6ff1c4fca84fafb2"},{"version":"10.2.0","sha1":"45330a9a77dbe36f11b14df7dbaf95f7bb109064"},{"version":"10.2.1","sha1":"ab1871f5412df8b6e1ef38e88ae1fbc36af0a07e"},{"version":"10.2.4","sha1":"e1425945fbb08d5bfcbcc9416133aef76e31670d"},{"version":"10.2.6","sha1":"a8cce2b78b3908c3f6e080db3cc02722a6e4685a"},{"version":"11.0.0","sha1":"4c1a6ec0a177e312cfc752660d3aed48872690d5"},{"version":"11.0.1","sha1":"7dd40bee95414c2ac0690e6fb25db950a88064f0"},{"version":"11.0.2","sha1":"608fb0e002ed0f5b432be492abd2d3c6c5d02d2e"},{"version":"11.0.4","sha1":"6fd849e6b8c3982ac13b7fe44ea9aeec237f63ee"},{"version":"15.0.0","sha1":"fc22d8b219fb2c7d72e66ddae4ffcc6ba1c2b362"},{"version":"15.0.1","sha1":"6685709c8e9493ee8b555402499dba3230caeaca"},{"version":"16.0.0","sha1":"3ef3f2f12bffd8c7d314b9358d02830e47eed81d"},{"version":"17.0.0","sha1":"fdb871b7bba817fdfc04fb91283400575a220b6f"}]},{"package":"play-services-gcm","versions":[{"version":"7.0.0","sha1":"e62eded7d61ee4b710ae3f5642d95f3d8be84246"},{"version":"7.3.0","sha1":"c9ea4ed631220a3dbc6e6ab2d68d46fc5e62b6a6"},{"version":"7.5.0","sha1":"4b703c730aade6bd6519caed44fe9a2920006109"},{"version":"7.8.0","sha1":"96366391e8f6f1ce1f9ca35f2175a2ee75b045cb"},{"version":"8.1.0","sha1":"d86d8b4c502c6169773b27496290ed5c0f294d73"},{"version":"8.3.0","sha1":"5595c06220f77a3e398d2a49d6c476dbd688d3df"},{"version":"8.4.0","sha1":"be200c97c64fd629fa1197b385f8fa3845ad6aa"},{"version":"9.0.0","sha1":"7f73c569fbadabfcf3c0a68fc7f80f2f0e144b1b"},{"version":"9.0.1","sha1":"185b8ae85bf1924fa2db161bf263d04e645943cb"},{"version":"9.0.2","sha1":"13a1dafe0a76ed1dfb6949df634426cc0fdf0166"},{"version":"9.2.0","sha1":"d1e0999514b9b1a57ed80023c476178c1cf0b86a"},{"version":"9.2.1","sha1":"9bbb1f0113a86a66454ac40499f4a62111258243"},{"version":"9.4.0","sha1":"60eab0d80a8bb8b7f690970bb8148c41ac7997ba"},{"version":"9.6.0","sha1":"d3f12feb399fd91e44764c0f44fcfdf42e5d8489"},{"version":"9.6.1","sha1":"8be2a9425875c7a1fa2c6857dec6e6646b077480"},{"version":"9.8.0","sha1":"8b6eef869afb9508185ea0ea638acf126d50815b"},{"version":"10.0.0","sha1":"e9874af7a6ccc1778cdf10aa8ae89eb6389f8384"},{"version":"10.0.1","sha1":"47d42facd4368962d9e28734ca9557609536f11c"},{"version":"10.2.0","sha1":"c01b5174db1bbfba78e03d4734fde4fe4baef6ca"},{"version":"10.2.1","sha1":"d441cc999a35ecd53a8832f09d9dde333ffbcfb7"},{"version":"10.2.4","sha1":"3cb04f7b464ff1bf5defba47ed3c09013ca3300f"},{"version":"10.2.6","sha1":"d035fdc68ba48e667afce3ea996edb447fe54f67"},{"version":"11.0.0","sha1":"a4fe8f3ae524e46ef744fd6baef76d8f8723b61f"},{"version":"11.0.1","sha1":"574f00c5e1a68a95ef36b1ac14cabd2851020ed0"},{"version":"11.0.2","sha1":"c1f9b1122fec1e672cff4eb57e483c784f542aff"},{"version":"11.0.4","sha1":"a5aead67c2de6a15e0f279b9ef7767419461850e"},{"version":"11.2.0","sha1":"d6bd18c011352242e251cb4348ad0223e9364cab"},{"version":"11.2.2","sha1":"9cfd8f2368444e1d4f0b3c7a62e315bb29eb6031"},{"version":"11.4.0","sha1":"6601440d7905958318cf6a435e3158f17be0505c"},{"version":"11.4.2","sha1":"922f6d88fb4340dea61ba650a2fe519f51369b71"},{"version":"11.6.0","sha1":"6450b1b6829cefe0d7d78b7402cffb0b3ee1d98c"},{"version":"11.6.2","sha1":"d80ea46e3f3712ed9c054237069d38d357b900eb"},{"version":"11.8.0","sha1":"78a361cb987811fddfd7f10edba3589664e35521"},{"version":"12.0.0","sha1":"a51fcf0a244698f9355f3d56189a55605e063af9"},{"version":"12.0.1","sha1":"d102356c7c5bafcae152d720d3a8ba8c2f06b0fd"},{"version":"15.0.0","sha1":"ded9326709dac409b56dff58c86dbd8659cd15e9"},{"version":"15.0.1","sha1":"68babf5e5750e7cdbaf7e726ad2a0ecbfe3c5d72"},{"version":"16.0.0","sha1":"365eb477d3382f526bbd824ecce7d9b708381c90"},{"version":"16.1.0","sha1":"fb3de732bc18f5a9d0f05cb2d4e01d59f4120417"},{"version":"17.0.0","sha1":"ca7c94434e4f0d900005caf1d6f48ae092d7956d"}]},{"package":"play-services-oss-licenses","versions":[{"version":"11.0.0","sha1":"a0808565e00463a7ce1dbec25d032b4e1d2ebe28"},{"version":"11.0.1","sha1":"80b7ca21237c54d43a25e9f9bee115d600e9ab48"},{"version":"11.0.2","sha1":"c5599d6d8a44da3fb829d0d159d3a4ab722822ca"},{"version":"11.0.4","sha1":"aec1aaa173b2e129a9d7389d94db4715b711bf8b"},{"version":"11.2.0","sha1":"f9fdd68f2d71173528720058a2753ac3862cdfd7"},{"version":"11.2.2","sha1":"155c36351da853f4bc2d5fa0db107809b8032eae"},{"version":"11.4.0","sha1":"d946953328690127db50e974ca73e8766337386b"},{"version":"11.4.2","sha1":"ec5249c201cd172dcf9479d57e11f7bf058bf1af"},{"version":"11.6.0","sha1":"637526b441c056d47f4b19ac601042ba5a34c053"},{"version":"11.6.2","sha1":"5b2cf54ce9caf9a2fc0aafcf8d9df90ff1bbdc23"},{"version":"11.8.0","sha1":"ddc2fcac103bdc7aa647f695d77f8b819436f0af"},{"version":"12.0.0","sha1":"a9645b25aeaa870917da0d34902ea009398c53"},{"version":"12.0.1","sha1":"b5edbbf0889555c423426b9f5edd6fbdfd157a89"},{"version":"15.0.0","sha1":"e3d9313764a660e31faf75bf7ce6b0bafa21eb5b"},{"version":"15.0.1","sha1":"86d11f81c77702de817c1e42e7f2f70ca6552147"},{"version":"16.0.0","sha1":"cd8ae8aef5391e85d1fac8ca1b617416869341c4"},{"version":"16.0.1","sha1":"9c30c484bec59c35d2c120353ade9a172f6e954b"},{"version":"16.0.2","sha1":"ff98526d12aa183cb8edea965bc9269c098b6a1b"},{"version":"17.0.0","sha1":"4f7cb3415c922bd9d36563fde52cb0344d8dc68"}]},{"package":"play-services-auth","versions":[{"version":"8.3.0","sha1":"e25eae18b969b55d725e979332645635f847fcfc"},{"version":"8.4.0","sha1":"696c17cbaf3f3f3bc5491ba8d9b1385e348c4347"},{"version":"9.0.0","sha1":"9957cfc3ae2e992750966fda058657dcf76f4fbd"},{"version":"9.0.1","sha1":"15b77ce05bdc0abab329ae1d96778b839ceebd6d"},{"version":"9.0.2","sha1":"34bdd3e6ede52f2f192b152519151748ee0ac16a"},{"version":"9.2.0","sha1":"6edcb5705241121bd9ec0f2be4026dbd4aea046f"},{"version":"9.2.1","sha1":"1afa6f2c5b596e1b1ac08871541ce28bbec5198f"},{"version":"9.4.0","sha1":"6e7104c69daa76f7cb2c1e37f08b10c7d03fa036"},{"version":"9.6.0","sha1":"18421b7a47157dba87207634d0df9d615a2acb27"},{"version":"9.6.1","sha1":"c938740912b3b841165db61b71a47fe1df359e6d"},{"version":"9.8.0","sha1":"aa8cab4623eef8256f9291e434be23b319f622de"},{"version":"10.0.0","sha1":"96eb5f54d78bb153f01b5c373ed8b01125698df2"},{"version":"10.0.1","sha1":"ff2a41b9c85d5e20c5f43388ef28b6b90f80b9e0"},{"version":"10.2.0","sha1":"8de8d533af92e7fcbb522d8ade5f5780be0e5620"},{"version":"10.2.1","sha1":"193080e0f2296648537b978f3c11ada2f55a5393"},{"version":"10.2.4","sha1":"7e5b4376766b0270a3c3188d96217102bf350fa4"},{"version":"10.2.6","sha1":"7592ed16b6dc2d71a920b6c9793de42219f80154"},{"version":"11.0.0","sha1":"4cee1aac1ce4691e94d4fb0501a314faac3c2cf7"},{"version":"11.0.1","sha1":"7e3daddfda0cde630184522c720b27394a4f0a3"},{"version":"11.0.2","sha1":"ae0eb74ebd4fae21dcb4e92b2bfaf7820027d8a"},{"version":"11.0.4","sha1":"8eadb8b02086c8d6c84ffb0b971a3a72f92d01e9"},{"version":"11.2.0","sha1":"d7b02cc8b6b9865a22841a5217a899a858d7613b"},{"version":"11.2.2","sha1":"80184e1835c3f5f9bd37163452c878c70a1d630e"},{"version":"11.4.0","sha1":"f596dbc7bcb6db59a096798f4f43e6960546454d"},{"version":"11.4.2","sha1":"941b1a3c9089d5fa2b3d31ce4e18a287f6a9a537"},{"version":"11.6.0","sha1":"8a1abca97886b7456c88a1d94374f96ac18318ef"},{"version":"11.6.2","sha1":"c370f62be30e083a45bffdda24becac5ba40317e"},{"version":"11.8.0","sha1":"b6c49b2797d8cd10f8edae65eeab9299df253a50"},{"version":"12.0.0","sha1":"3e916789068738fea50ee313072e23cd16e289d4"},{"version":"12.0.1","sha1":"7b9b9e64f986ef7abe59e9bd525a90a30ae29636"},{"version":"15.0.0","sha1":"2ba5d1b77a6c09a583b89caf26f7b81b37287c6d"},{"version":"15.0.1","sha1":"8f0a741e39e28aac4ccb486ffa99aeb252758940"},{"version":"16.0.0","sha1":"7718a16e0dff2e31c7b1d7be2af3e91ac62d34ed"},{"version":"16.0.1","sha1":"50e2949924602eb1a664fe11e97478e465374edb"},{"version":"17.0.0","sha1":"658c692a96af58d62893b1c2d9742c48a64e4512"}]},{"package":"play-services-cast-framework","versions":[{"version":"9.2.0","sha1":"547422c2c76717283e860e529b0a0759467852ac"},{"version":"9.2.1","sha1":"35b2b50b8cba760d5109ac53cbd3d5b3fe8ea8a8"},{"version":"9.4.0","sha1":"2bb9b5bb9364b9ceef080a9aa7c0d95467a8c6ba"},{"version":"9.6.0","sha1":"b00f3cfb86125340a910dcf4b441269c7e5198bd"},{"version":"9.6.1","sha1":"5c75a3ff7a0e9fb90fa3e2bcbba79ef402f2b320"},{"version":"9.8.0","sha1":"350c90b0be2def7a16a9f25dd7a7d9ac0e4bb4bb"},{"version":"10.0.0","sha1":"e53bc2c5d4eb5e3d2f2ab648b8571ceb6be2431a"},{"version":"10.0.1","sha1":"8f8213b7c7c6f94cfa3c3c75961e64946d1b45b4"},{"version":"10.2.0","sha1":"b92bb173c51788d5ff88957d084d48a720d00501"},{"version":"10.2.1","sha1":"2bc7a57f244e7a27dc8e0e79c0c45b6a306262c4"},{"version":"10.2.4","sha1":"c5f0523162a0dc9f50a5ba4ecf787b75b6fa20c6"},{"version":"10.2.6","sha1":"56a4917d271ab1c6a24a5f35ebe0b3c44c74fb64"},{"version":"11.0.0","sha1":"4b4a8b32fca5eba59886fd23a87490dd6d6cc716"},{"version":"11.0.1","sha1":"405e99995b7c75c8e37a2073a0246589f4fe5973"},{"version":"11.0.2","sha1":"41de37d9d851f2cf186678afa8d8c64e48658a0d"},{"version":"11.0.4","sha1":"da97c03bbbe233d3f0c21d1800dc70556d065189"},{"version":"11.2.0","sha1":"3ea108f733fc1c20bb65c36ec91717550020b28d"},{"version":"11.2.2","sha1":"a992968912a9b8e5f2c5f263aa1ac857bab65f98"},{"version":"11.4.0","sha1":"db90180e9eac5eee2756fb0e901e7ac4d0b42a53"},{"version":"11.4.2","sha1":"d3aa2fc52835698d0a99e4ec90cbace6c00810b5"},{"version":"11.6.0","sha1":"60728c7d0118bdafc05efd851fce251af895c50e"},{"version":"11.6.2","sha1":"582920f9c4dea8e3489d86242c706ccd3e0b4ea"},{"version":"11.8.0","sha1":"486e131b395264610d10010e33c0369f0eec0528"},{"version":"12.0.0","sha1":"4fa453bd7cde09e3d9cb22a12cc0effe4380751b"},{"version":"12.0.1","sha1":"428d086d4baffbf9e7984dbc9355fa4834d7e4fd"},{"version":"15.0.0","sha1":"d24fc843c3d3ea5f3afdd2b7f9a75920f96dcd9f"},{"version":"15.0.1","sha1":"2e9e3273ebc6c68cb54c129088607c5c037f5da4"},{"version":"16.0.0","sha1":"58c7a53bda1dc25792495e76c5f5c4384c364c10"},{"version":"16.0.1","sha1":"23f0a66af2534826eb69a7bc4a061444bd991258"},{"version":"16.0.2","sha1":"363451c0a46353e5cad14f7cfd2e2aaccf4f537b"},{"version":"16.0.3","sha1":"9292d453600f03dfa5babce39da1096334ee4733"},{"version":"16.1.0","sha1":"715597b2b4f89d1b41d371402d1675db54396121"},{"version":"16.1.2","sha1":"5faef20f71dcbb025e77779bd3551ad5a1c4ae44"},{"version":"16.2.0","sha1":"4ab564a7f64e4f43e1698e3649d989c9b1384a00"},{"version":"17.0.0","sha1":"c84f5a15cfcfdaff22b8516731c2d63d58b325db"}]},{"package":"play-services-fido","versions":[{"version":"11.2.0","sha1":"8d7106673554cb17944be9424d7411866fd112a2"},{"version":"11.2.2","sha1":"5a671d7177e1ffad3f9fa1f15fe3a54091c26130"},{"version":"11.4.0","sha1":"c2e36baee383a4001de66544583f03ffcf8dcd1c"},{"version":"11.4.2","sha1":"e9351dd71b7883fed0f3bee9bf8a9796f88dbdb"},{"version":"11.6.0","sha1":"9bfac398daa5adfb34014b2ad893d64b4b054bf0"},{"version":"11.6.2","sha1":"fe2db5be72eaa2921fa29fb8853dfbd1759198a1"},{"version":"11.8.0","sha1":"4f1f2e2163484ae20f0e3534f908b3dfb1fd602e"},{"version":"12.0.0","sha1":"88e23fc147751a20f49f573fb39e9d1926a918d8"},{"version":"12.0.1","sha1":"3ef824617910881526cfe56167fd4f9a926ad0c"},{"version":"15.0.0","sha1":"23827a07e3c0b990984b1a1b24ea8f536ae962c3"},{"version":"15.0.1","sha1":"ccf24a9c68cf8663c69d625204887c4fdcd736cb"},{"version":"16.0.0","sha1":"4f43c25b8cfc9105c53e075c9bf40e11754b2ad"},{"version":"17.0.0","sha1":"5c1b33b88660936131f3e3d195b0ba2187e760a5"},{"version":"18.0.0","sha1":"81948dfb2df7cbe7acb9441d120f6eb43c099193"}]},{"package":"play-services-plus-license","versions":[{"version":"11.4.2","sha1":"a0954e2a2baa9e3e57fff030ffa4058e62fb3d84"},{"version":"11.6.0","sha1":"431f3f58db1b7e96387004619029614dd692323c"},{"version":"11.6.2","sha1":"9db0cc2543631a05de383041d5a1439b4040e851"},{"version":"11.8.0","sha1":"23e1be742a33a57c63e7d5685e1bac98db1bf0bb"},{"version":"12.0.0","sha1":"b01daaaa5935dcd9bd1f272fd0f0a9de0f2fde2"},{"version":"12.0.1","sha1":"43705fd25ce4b6e64f0752ad9d27a03a8cb1610c"}]},{"package":"play-services-panorama-license","versions":[{"version":"11.4.2","sha1":"afe491139112ec38baf6efaf3407f2d1e7fc72a4"},{"version":"11.6.0","sha1":"fe26f2d32b87fe2118171d2e4df3114ff2f3c6f2"},{"version":"11.6.2","sha1":"fc6b975071d7f18a67314f0a01c2f3b15b6b4a6d"},{"version":"11.8.0","sha1":"887499486ed3e2f9840b5f1cdda58ce4f70fdb01"},{"version":"12.0.0","sha1":"c0ef11dcc24bf5f770215de2b538ece04eaad235"},{"version":"12.0.1","sha1":"efed00861fc149642c7ba728270e6bb2deb93f3c"}]},{"package":"play-services-auth-base-license","versions":[{"version":"11.4.2","sha1":"7ba1b89bfdccfa0f7e20d1ebba49f92d525dcda"},{"version":"11.6.0","sha1":"c122d0330faf0662cb133ecc46a37ae7692c61e4"},{"version":"11.6.2","sha1":"1d25a9f2b384f09b493f73e25e011921420c9863"},{"version":"11.8.0","sha1":"e57906efb2c5e5d41d73910a86aa1fecb5da18aa"},{"version":"12.0.0","sha1":"d59b58d75c646eed7301a08a5c85c9dca147e2d4"},{"version":"12.0.1","sha1":"8e7d20bdecc7e59deb81bfa87a74b1a39938ecd6"}]},{"package":"play-services-maps-license","versions":[{"version":"11.4.2","sha1":"4f6992af8eeb1187b1c8a225fd2737c3ea224233"},{"version":"11.6.0","sha1":"5b49225e59781cb27c42459da9d47928e7d3cf19"},{"version":"11.6.2","sha1":"219b0750eb15b3a21d2919614aa297230132f690"},{"version":"11.8.0","sha1":"56620053e3047e0f2f92dcd22639b9e07b0e4314"},{"version":"12.0.0","sha1":"15ed2f9c7fbe9beb8d2865b0c3664dad5c2b1eab"},{"version":"12.0.1","sha1":"89e87bc7813e8d2c1d21ddfba3fd8a2f8a7c29d2"}]},{"package":"play-services-places-license","versions":[{"version":"11.4.2","sha1":"75ef914a0d91a575919498d7d75edd73410e9350"},{"version":"11.6.0","sha1":"5f60e64858b94e5fd127c8800249f1ca75e7e840"},{"version":"11.6.2","sha1":"73e26ffe5795ce62af47918e4f3374365998aeee"},{"version":"11.8.0","sha1":"1221f393071fdcc83a581a2fc219536aca78d86f"},{"version":"12.0.0","sha1":"3477e771c985d8992cd85386bf1ef34e54c84c19"},{"version":"12.0.1","sha1":"924729a4ff18076c29404a6f4aa7908f76e5241f"}]},{"package":"play-services-nearby-license","versions":[{"version":"11.4.2","sha1":"509c9a0f77533c48a86c1347a32f3d42556a0aa2"},{"version":"11.6.0","sha1":"44cecb685387846ff8448c9a4fd6e6bc079c5d79"},{"version":"11.6.2","sha1":"a114d327aef6954a74b109d1d45976e6a7ab52ef"},{"version":"11.8.0","sha1":"50dba7c2a040c584b7a3812e16107dcc47bca5bb"},{"version":"12.0.0","sha1":"1c359ed93a4e395821d8a623dea2b8e04fb9cf7b"},{"version":"12.0.1","sha1":"57c1171028c97bdc1481d68f4e6cf78de23f0d31"}]},{"package":"play-services-games-license","versions":[{"version":"11.4.2","sha1":"c3bc5e3976fea848beead786cdea2b7fe97d6a8e"},{"version":"11.6.0","sha1":"7f0b9b0686d519bfda142ebcea6861c04228ef50"},{"version":"11.6.2","sha1":"1dfef826b576f923e71c5ed889e73bc2c3cce1db"},{"version":"11.8.0","sha1":"147a2fa22b274841c59862bef1e93ffb7209ce9d"},{"version":"12.0.0","sha1":"78943ff669c275d96e343ea649a612b62d1d949"},{"version":"12.0.1","sha1":"fd82e563a9f52ef7c811f838452913d024e197c"}]},{"package":"play-services-safetynet-license","versions":[{"version":"11.4.2","sha1":"fbd57e196a71ca64a25441bc052bb83f0c685f67"},{"version":"11.6.0","sha1":"61a0d42d0e4f059bfc18017999f246d8fea3fa61"},{"version":"11.6.2","sha1":"d276fbd0f5e94a79b98631186191ed4d2c61cb71"},{"version":"11.8.0","sha1":"7c13125c7cf5d6fb7f4d297b15152e3664ed677f"},{"version":"12.0.0","sha1":"a18c386ff036ec0f37ab39cd14e4de258956d2c"},{"version":"12.0.1","sha1":"31d402c49629d26eb727d1a26b9f4d1eee3568c3"}]},{"package":"play-services-vision-license","versions":[{"version":"11.4.2","sha1":"328727ec41f96df8d20a88bd36ca0817b0e77de5"},{"version":"11.6.0","sha1":"60cdc19413e35eca8d71f003bf5efa25db413079"},{"version":"11.6.2","sha1":"73e7003bd7cdec855b53d1c90b5e3c489b334ec"},{"version":"11.8.0","sha1":"fab1c132af894ba971cd287e39d728d863b48e04"},{"version":"12.0.0","sha1":"f29089ed16f1ab2ea78f544fcd75ba9e4af32a48"},{"version":"12.0.1","sha1":"833c9996a7acb992bb7ff06ec4278302fc87eba3"}]},{"package":"play-services-fido-license","versions":[{"version":"11.4.2","sha1":"5094da708571d872d6187e8ab1596a0a16fc60ff"},{"version":"11.6.0","sha1":"1a8b9f8fa33c12384279997d2cb5c6e9fe72afee"},{"version":"11.6.2","sha1":"9f09df435ee261ee9193b8c47829bfdc50940bf8"},{"version":"11.8.0","sha1":"d914e4a4e52ce5b069e3ebc104f5d1cdb7e9cc3f"},{"version":"12.0.0","sha1":"816e0c5e01795008c170e4b5544beaeefd131e5d"},{"version":"12.0.1","sha1":"d338163489546a1ec96f53950b6bdf7fd4648882"}]},{"package":"play-services-drive-license","versions":[{"version":"11.4.2","sha1":"e871b1b09d3f475a5dd2de444deeb3c50619571a"},{"version":"11.6.0","sha1":"ce64de8f6e94cf79794c13b4c3a1306021ee01d7"},{"version":"11.6.2","sha1":"816838da9a3b2abfd4511513c130331523eac5c9"},{"version":"11.8.0","sha1":"4398003675853d1c1af2fe49d34172445c454150"},{"version":"12.0.0","sha1":"dca41c5159b92d95e863968f5314a6b4a3f7feeb"},{"version":"12.0.1","sha1":"41a4048e8230dd7f359898252f0473799037aa0f"}]},{"package":"play-services-auth-api-phone-license","versions":[{"version":"11.4.2","sha1":"ff448360463f4fd9e5a0c76472b53dec581e7c5"},{"version":"11.6.0","sha1":"adab4613da9413a13fa9ced4ec44eb4ec8af9a6"},{"version":"11.6.2","sha1":"b3a9452566bcac101fbef97d454f8b0fd327defe"},{"version":"11.8.0","sha1":"590d7e6fcb28a335e5a9f9fe7baebf282a891c11"},{"version":"12.0.0","sha1":"62d9aa12cd0078a6f84dcc40b72b95c0fda4a897"},{"version":"12.0.1","sha1":"6580d05fd991a92353f2ff66a400b53c2baed369"}]},{"package":"play-services-oss-licenses-license","versions":[{"version":"11.4.2","sha1":"b9ffc0895636b98e1cc5794bbed7bd7c78be09ec"},{"version":"11.6.0","sha1":"dd8d31864822e2a885fcbaa107abf359745ea736"},{"version":"11.6.2","sha1":"92c5c98604eb5da82afb1d3145a5a9cfd7d40202"},{"version":"11.8.0","sha1":"a32bc7c84c76294875662e0840351f451a5336bb"},{"version":"12.0.0","sha1":"61fa6c0380551612a57c870d6a1421084c006cbb"},{"version":"12.0.1","sha1":"edb6b8275af589fb9f07f0a0fdb0e4e13202cc1c"}]},{"package":"play-services-identity-license","versions":[{"version":"11.4.2","sha1":"8f935cc1927e95018b78d078a88fefafb1eebee0"},{"version":"11.6.0","sha1":"2fe65130298498ffbbf6472113be01c584face17"},{"version":"11.6.2","sha1":"39102773e91ea3a0cb85c0cb5b4b759af05918a7"},{"version":"11.8.0","sha1":"cb70564d491d728eb9cd5bf52a9fc58bc770eec6"},{"version":"12.0.0","sha1":"435ddf6886d7844687c167994e11cc4fef6524aa"},{"version":"12.0.1","sha1":"45a2582c465f66b1a8066aee6ee6633a325bc708"}]},{"package":"play-services-fitness-license","versions":[{"version":"11.4.2","sha1":"67d25d04d2ae86ab75e8146b20da964bf3da1559"},{"version":"11.6.0","sha1":"cb20fa5529792431c6e586604df392a86f474978"},{"version":"11.6.2","sha1":"e1f0e3091322f8e535db72869112405b397eb8f9"},{"version":"11.8.0","sha1":"a548eee9dbd0e0c6e9cadae64b5af321bf331de5"},{"version":"12.0.0","sha1":"dd13f1bc3ef57f6884a3ba76424f16c60298070a"},{"version":"12.0.1","sha1":"ab05d901b1612728daaf047c584a7136a177c653"}]},{"package":"play-services-wearable-license","versions":[{"version":"11.4.2","sha1":"14e4d0fab0912912a986a05385952f9d5b35376"},{"version":"11.6.0","sha1":"acb08261b0d5af22aa6b3b00ac1248e20d3cbb48"},{"version":"11.6.2","sha1":"9636647d27c77b2a24e8da4b03b8c2d41c038e24"},{"version":"11.8.0","sha1":"97f99a381361bd36fd1d8c3199a65c1f68fea7ab"},{"version":"12.0.0","sha1":"e6779fd1923183477d9e877c411cac5aed16c250"},{"version":"12.0.1","sha1":"5fb8c6a693d16341d9550468ebd2360ffcabae36"}]},{"package":"play-services-awareness-license","versions":[{"version":"11.4.2","sha1":"cb7f9a23ed1cc3dd3cc0dfdec001143f8ac3b9b1"},{"version":"11.6.0","sha1":"a84373affa80d637edb91d0195bdb0ae68b1e1d"},{"version":"11.6.2","sha1":"609f242596b55265bc0a4ca2fd2def8f8adc9056"},{"version":"11.8.0","sha1":"5e275430429e6bd0b13b0619e6031634fc70246d"},{"version":"12.0.0","sha1":"6f1db195e80e2cb5a54aa636581231914d18104"},{"version":"12.0.1","sha1":"2853af9c864e889acd0904340cbc416ccd1e5b08"}]},{"package":"play-services-cast-framework-license","versions":[{"version":"11.4.2","sha1":"f94c6a65c11410d337e80b61d147fa79842f75fd"},{"version":"11.6.0","sha1":"a77532010a7b7b4ada5ffd7f2e07a0e6f5c3289e"},{"version":"11.6.2","sha1":"36ef2075a423c94d3ffcd0928b89181d703a2fb6"},{"version":"11.8.0","sha1":"612ed3f4a964e69716a98b855166897cfb012342"},{"version":"12.0.0","sha1":"72994d74ed3ebacbc5c7b753d123d0e8ff5c81d4"},{"version":"12.0.1","sha1":"d9e3a0674cd9007a7d7dfa05618491e90994b631"}]},{"package":"play-services-analytics-impl-license","versions":[{"version":"11.4.2","sha1":"73cbf3435c0793816ec705bb257c7798738a83d4"},{"version":"11.6.0","sha1":"b3b09593efc407e9d63cdc76241bb292b9bf3228"},{"version":"11.6.2","sha1":"4760b8df6583652654489d9ae328001d97a95894"},{"version":"11.8.0","sha1":"51dfa03e8bf8f211a5eeeec7e2c8ab020bc7cfb3"},{"version":"12.0.0","sha1":"e09c195b1390f335ac20e02eef18627411a5a206"},{"version":"12.0.1","sha1":"4d05f6b96830fe8da3e897f79fc653834dca1a9f"}]},{"package":"play-services-vision-common-license","versions":[{"version":"11.4.2","sha1":"15088553323ccba4af0994aa965dec0d29fd00ef"},{"version":"11.6.0","sha1":"aae3287d643c1bdd916d8545c7147bd75daba4c5"},{"version":"11.6.2","sha1":"68320793be5604925ef2ec363a8e02d9e0f21c47"},{"version":"11.8.0","sha1":"98b634192485ef0f5df215a432a065d41256795d"},{"version":"12.0.0","sha1":"23b39f726efccdb1c90b8876879e964ad1f2eedf"},{"version":"12.0.1","sha1":"5b5e6497445f4956235fb919cc39546b8428040e"}]},{"package":"play-services-tasks-license","versions":[{"version":"11.4.2","sha1":"87b43ce594b388cd1eb760316c0bb4d8fcd91cfc"},{"version":"11.6.0","sha1":"1d1466dcf86f73b129c0cc9a3d4d005daf27e85a"},{"version":"11.6.2","sha1":"d0e6897e0c7547f8bef12ac5a74a826a7fd444ac"},{"version":"11.8.0","sha1":"2e33f3319bb097c3a796b322a7335ba2a7687ad4"},{"version":"12.0.0","sha1":"99e857c79a20eb16a00bf156fa2d736f69817bc9"},{"version":"12.0.1","sha1":"6798c86bb38f75c3d59f27b068726ff19e8e70ea"}]},{"package":"play-services-auth-license","versions":[{"version":"11.4.2","sha1":"a93ba7fbc228754595102a937087eef0103f1309"}]},{"package":"play-services-iid-license","versions":[{"version":"11.4.2","sha1":"d00093c085a55323a44e1427c452addc909ad6f4"},{"version":"11.6.0","sha1":"99c5e93ac818c9a385a0dd722d3be33cd7d8278"},{"version":"11.6.2","sha1":"d391bbad75003cd5b1192e9c20429133406399b1"},{"version":"11.8.0","sha1":"e8637617def0d0e4facfe7a9150b3d77409bcca"},{"version":"12.0.0","sha1":"816d72a1816561d1f452f374ab78a2407b08396d"},{"version":"12.0.1","sha1":"958f1f46e96daa803820043d081e49aa18a61a9d"}]},{"package":"play-services-appinvite-license","versions":[{"version":"11.4.2","sha1":"99ab69537f15338245f9b2000f373694d873ff73"},{"version":"11.6.0","sha1":"364ef3fe87792c805a7d617877e6a3b2e5b0539b"},{"version":"11.6.2","sha1":"aa920aa2b3e6257cce6ab0a0867f0d1fce716c9a"},{"version":"11.8.0","sha1":"db625570c1a64bdefd2b91e57e924ad94cb1b199"},{"version":"12.0.0","sha1":"144ac1ae856854f4f9d8a8bf1af4951881487acc"},{"version":"12.0.1","sha1":"8499fde91ca7dff438a7fd7481b7cde7289af4ba"}]},{"package":"play-services-gcm-license","versions":[{"version":"11.4.2","sha1":"16854d545623772544c189e89c26505f415ba285"},{"version":"11.6.0","sha1":"4144e3287b97e293c8dc05e433adb567611da665"},{"version":"11.6.2","sha1":"96bb1f958229316e1da95c85d328462f6f980b4d"},{"version":"11.8.0","sha1":"ffb96fd8830a0c1b465ce0a295941e65e1b77e82"},{"version":"12.0.0","sha1":"299586292d39f5e4abb4bc454fed994e5689a32e"},{"version":"12.0.1","sha1":"3bdca3e00a0f84ec349f7d3b07ccdec1a6d7a413"}]},{"package":"play-services-ads-lite-license","versions":[{"version":"11.4.2","sha1":"d29c4466f60c12172ca770f61fb52f076337dbc7"},{"version":"11.6.0","sha1":"15762432a76a1d4ab325ff0daf27c6ad70f5be6a"},{"version":"11.6.2","sha1":"62d3e5c14e08188a9f9bb0826bf5a18a6c9aab25"},{"version":"11.8.0","sha1":"a98afabab91614068c641b98477f7c45de2efb87"},{"version":"12.0.0","sha1":"48f299098eb16c8d97aad930048b26759501a467"},{"version":"12.0.1","sha1":"f8a9c05e6b6369495672bc12d4bbf2afa6abd987"}]},{"package":"play-services-ads-license","versions":[{"version":"11.4.2","sha1":"bed4b9ef3633a93ed530eff917362181a5d76ea5"},{"version":"11.6.0","sha1":"b888263e3bdc12f42099aa1f7749b8a7a3924857"},{"version":"11.6.2","sha1":"831ae1bf1fa7d5418b751eda2ad15f94e2be5f72"},{"version":"11.8.0","sha1":"27d50a2d3e6f940716999e5bb7476f6a7088fe98"},{"version":"12.0.0","sha1":"bef5aba3e394e4f04360d42ca304825fb90a690e"},{"version":"12.0.1","sha1":"c3b488e7de2be554dacf4b2cabd2a2a33b13407"}]},{"package":"play-services-tagmanager-license","versions":[{"version":"11.4.2","sha1":"de814fb1cbd79501ff2ab767a1beb09e44dd8c72"},{"version":"11.6.0","sha1":"7493b6eef1c56d8b9a0a8dcc3e5a5cf42a8e0817"},{"version":"11.6.2","sha1":"dd186cee995302fb6e0f468ec896c99373425e56"},{"version":"11.8.0","sha1":"991f6010526fabc3e0faff276d37984f367ec08e"},{"version":"12.0.0","sha1":"d8a7bf2e2fdcf88f8abb9b865ad163746965e24d"},{"version":"12.0.1","sha1":"4430e4db684e1f2b9b12f36f755824fbc2d54bda"}]},{"package":"play-services-cast-license","versions":[{"version":"11.4.2","sha1":"653962c6a918bc8d6e2182ac9ea0cbfaf8ce0ea3"},{"version":"11.6.0","sha1":"4d8cfe1774feeca48a378fc8a94b91efcb13456f"},{"version":"11.6.2","sha1":"8dc85fbfc9d59088e5c47746b64d553ef5799f60"},{"version":"11.8.0","sha1":"32611552e0d1941d147f63f902ea2c7bce8e4ae9"},{"version":"12.0.0","sha1":"1dca4139ae3e2a122a99afeb2f368005c7b72efe"},{"version":"12.0.1","sha1":"3efdac265d1c3c52d836806f94cf195a9fab4e42"}]},{"package":"play-services-wallet-license","versions":[{"version":"11.4.2","sha1":"cfe1c0d7fb922b2ba8877c96decaf79d66367fb2"},{"version":"11.6.0","sha1":"20fdc19c8b85917f162307854092d40c202fbe73"},{"version":"11.6.2","sha1":"ff64d8938bbb9caac24488d9302a23c60b69b68e"},{"version":"11.8.0","sha1":"52dffe1a2f20cb4a10f8d2d84d1dec070420e7cd"},{"version":"12.0.0","sha1":"6d4e494f441e0b5ab9dd670785a0db00c91fed5b"},{"version":"12.0.1","sha1":"b2424d93c5907731dd783ca7f1f5df9d330d7036"}]},{"package":"play-services-analytics-license","versions":[{"version":"11.4.2","sha1":"efea0a47df07a375768eb41dd332880eb64cbed1"},{"version":"11.6.0","sha1":"52ff852423eab4e5846c9f67784008807e142780"},{"version":"11.6.2","sha1":"1cbe181b8fe3963b9f1535a4e0cd568f50cbde21"},{"version":"11.8.0","sha1":"5a0b4c12f0e1a2f7fee7f2a921e5ae9b8def78f1"},{"version":"12.0.0","sha1":"e690d3f4bd846a741af8cadad38f991c679f9586"},{"version":"12.0.1","sha1":"e6f7d7f0cab3c6420b6720f16470d34467a24759"}]},{"package":"play-services-instantapps-license","versions":[{"version":"11.4.2","sha1":"d4d24b40f2011727ea2a640d725ab68349113e76"},{"version":"11.6.0","sha1":"20a720a6c3aefabce9ed0f23b7372c97d3d54aa"},{"version":"11.6.2","sha1":"fbc9ad364cd013bf62a53895f8cd71bdadfc836d"},{"version":"11.8.0","sha1":"e84fa0c7f11ede0476e9cd52a05f52ddfaafb70f"},{"version":"12.0.0","sha1":"a3a377cad5a73267b90ec72b5112999ec334eaf7"},{"version":"12.0.1","sha1":"b0508ebc8c7f3f65b5b5e8ca46b22b1121e59d04"}]},{"package":"play-services-tagmanager-api-license","versions":[{"version":"11.4.2","sha1":"6f8229604f040f0391035c676696efca4c201e1b"},{"version":"11.6.0","sha1":"230ecf2a5732892427059f73ee7f091eb377612d"},{"version":"11.6.2","sha1":"725f917d97de77d11234caad6107c565b8855adc"},{"version":"11.8.0","sha1":"92e65dc0a4a6546c3cb38d6302e9a6d9b10a69c5"},{"version":"12.0.0","sha1":"50410568eb9f879c3a6b9242a61ef9e687d0134f"},{"version":"12.0.1","sha1":"89d18b6abb3da65dfa3e40e25a4da34d20616ca4"}]},{"package":"play-services-gass-license","versions":[{"version":"11.4.2","sha1":"9d8e2e709d4cd7aa9991b2001747bd374b123c80"},{"version":"11.6.0","sha1":"6becb969af4e367f027b3cc7c06b3992dd37624d"},{"version":"11.6.2","sha1":"2b3627525d32bacf9f435e8192c467d3a5874c0b"},{"version":"11.8.0","sha1":"22c6723b6ce035b2b41450fbeacbb7fd6f41733c"},{"version":"12.0.0","sha1":"1864a21cf95129201de7dfeddf7c4c4ae999b48"},{"version":"12.0.1","sha1":"8a6d6edef4b5f4281b71189fc2437ff77c7b4110"}]},{"package":"play-services-base-license","versions":[{"version":"11.4.2","sha1":"cd3b2812166b98cb9d8ee49623ca545792b031e"},{"version":"11.6.0","sha1":"10d855e1e96b12a3a73973d8c1e7943a2573ef"},{"version":"11.6.2","sha1":"46d3d88f296092077807584f1c60ea295aab421e"},{"version":"11.8.0","sha1":"162f1336bb10f73637fa80b90978aed85c7d68"},{"version":"12.0.0","sha1":"b56ab0bf1d0e4466c4b26b2082a50c834ca158ac"},{"version":"12.0.1","sha1":"ece67f22262655a1cac4bd7175f1b2ea5c4dfd89"}]},{"package":"play-services-location-license","versions":[{"version":"11.4.2","sha1":"5cd21dedd741116ecb7f934e4a11ebce9aafd853"},{"version":"11.6.0","sha1":"45b8e4d737b89189c2a045fc25f625c0a262d6f4"},{"version":"11.6.2","sha1":"5eea065f3eaa4a7e0e2a4258815b4e3fc1eb1fae"},{"version":"11.8.0","sha1":"ccd1be3bd6b7b97e2eb9c03c262721ee3a78298e"},{"version":"12.0.0","sha1":"578c82f9a689443b590333a94c21bea140ad0836"},{"version":"12.0.1","sha1":"24ea1b76685619b204b005279b9561c098423c4e"}]},{"package":"play-services-basement-license","versions":[{"version":"11.4.2","sha1":"1a02e3ac8b803598edfa82f67324416ef1cab5f6"},{"version":"11.6.0","sha1":"301afe4879d83b20e809733e815619f782d9f869"},{"version":"11.6.2","sha1":"62062301dbfb363a630614866e4dd5a716644f76"},{"version":"11.8.0","sha1":"c4fec4ec0f7625e3bc1078c3b19852dcd7b9b23d"},{"version":"12.0.0","sha1":"247d5e1c8e324034d9451332cffc1f8dc4fe1600"},{"version":"12.0.1","sha1":"745569b74aff259c48a460c519ff513f8d98a2b3"}]},{"package":"play-services-tagmanager-v4-impl-license","versions":[{"version":"11.4.2","sha1":"b2e2d3ddfe053a61d426fa7e4898e417d2275fb6"},{"version":"11.6.0","sha1":"8dc19ac9e0123ec5414808eb0cb8f409c7ff422b"},{"version":"11.6.2","sha1":"145d3e54ea8c7a0465eef8ce20525e7466f58725"},{"version":"11.8.0","sha1":"8dd83bd371630616f8eb2ad6b8e733056c1836ce"},{"version":"12.0.0","sha1":"711c4aca9359e8fcb79cb8e0ba6bf4719a996dd7"},{"version":"12.0.1","sha1":"499af6c100e2ce4d47bc4e9410c39b37f3745236"}]},{"package":"auth-api-impl","versions":[{"version":"11.6.0","sha1":"daebb711d52b675321a1221b313118dcab18fc78"}]},{"package":"play-services-places-placereport","versions":[{"version":"15.0.0","sha1":"8d445688cb11fd08bc1d41f8f36d0746c2d29d7f"},{"version":"15.0.1","sha1":"8bb4fe286df11ba41348bda4415083d2a7ee5e24"},{"version":"16.0.0","sha1":"a2df8969a555721d19a517edd09b97d0abcbb085"},{"version":"17.0.0","sha1":"6fd0e52de8c47b7228223bf3a7fe53d075b6b0b2"}]},{"package":"play-services-measurement-base","versions":[{"version":"15.0.0","sha1":"8649bb17c8fd4474ed15c201e06bada542b850cd"},{"version":"15.0.2","sha1":"ec7dd29418e4239de9d97fa7592684242773f9ef"},{"version":"15.0.4","sha1":"ad83d47159af10576f5e9662e04276b7d7bb13d3"},{"version":"16.0.0","sha1":"b90f15c0d9d5d46559d6ba07250ce8dbecef89e2"},{"version":"16.0.2","sha1":"5f759ca8d81e126cbdad86c21d3fe43101d43070"},{"version":"16.0.3","sha1":"919d991cbf069bc569750c3d3b51813131046569"},{"version":"16.0.4","sha1":"fda1198e9cc38b72b4a2d1a55b84825d30f72023"},{"version":"16.0.5","sha1":"6a621c4de6759972e228976482149ca356749989"},{"version":"16.3.0","sha1":"830e303d861feb307b57bd0389a2cd8d2c0d38c5"},{"version":"16.4.0","sha1":"6704af983ff484df1c5fc87db9c8cdea939909ea"},{"version":"16.5.0","sha1":"98dc8a4800e0a7b5b46d6d4df82f6d5274176539"},{"version":"17.0.0","sha1":"e24bdd6a9ecd063ec6db12f481cf0e1ad9d23cf8"}]},{"package":"play-services-audience","versions":[{"version":"15.0.0","sha1":"29fe88ec9c6bf71c1e18d13a15e29bb7eb2e79b4"},{"version":"15.0.1","sha1":"80adf3b63e34d3ded20a0d77394ba0adc42c20e4"},{"version":"16.0.0","sha1":"177fc04f7aeb65a2b8529dcc9982a2f63969acda"},{"version":"17.0.0","sha1":"22bfda95ada9185becdd5431fab77cae491bab2f"}]},{"package":"play-services-ads-identifier","versions":[{"version":"15.0.0","sha1":"fd6fc22f616716b8f486d045169f3af02da408e8"},{"version":"15.0.1","sha1":"a120ac9fed5e9430247d54606bee6235527266de"},{"version":"16.0.0","sha1":"aa9be39d7fb67d7e7d6cee7395186d46a62e0689"},{"version":"17.0.0","sha1":"8989aabdf3480ed2b6ebae27ac75c12145d9a29b"}]},{"package":"play-services-flags","versions":[{"version":"15.0.0","sha1":"7b8655d0eccaaf52f5d9a3d8b21d4e87e16e7b10"},{"version":"15.0.1","sha1":"10f4153bcd8e271aa701f49824d718a0e827652c"},{"version":"16.0.1","sha1":"40ec115409a56b9f60e947f8c226240449bcdd8f"},{"version":"17.0.0","sha1":"6b6c8aba507f8f89f02593b28fce17b1dca08adc"}]},{"package":"play-services-phenotype","versions":[{"version":"15.0.0","sha1":"f2a87e111d2e1b9924c5c24cf808a74aee328bb7"},{"version":"15.0.1","sha1":"2d15f1314fbfce523b4c57b70dfeb04416cf3621"},{"version":"16.0.0","sha1":"693386367b6e26f757a92702458bc9fa018f2c27"},{"version":"17.0.0","sha1":"1e07cb6ee559098056aeaedeb1191bb21304e059"}]},{"package":"play-services-ads-base","versions":[{"version":"15.0.0","sha1":"66df0fafca44106ff3e3c73277e87653d596f78c"},{"version":"15.0.1","sha1":"4995bc25a34f87d9bbb7461f0c4e1964e6f63ac9"},{"version":"16.0.0","sha1":"9239470b2317f3e29b47d9045e154f6dc2a7afcd"},{"version":"17.0.0","sha1":"c6eab6c06cbafdc895dc3166c5c6a2df3e0f726c"},{"version":"17.1.0","sha1":"6cfa56ef005b5e6bfa5d34d62d6bb044e73426c"},{"version":"17.1.1","sha1":"e157b6e9f7c1a3f85bc8b88f87483fb61f860373"},{"version":"17.1.2","sha1":"2efe8b43b69e7eb813a713019409e0826e74a2fe"},{"version":"17.1.3","sha1":"e1548e32419725d82323bce36b9a8fa75bb35c72"},{"version":"17.2.0","sha1":"8d84fbed5ad4b2800d856b137395c9a28edea67a"},{"version":"17.2.1","sha1":"80bb2e5a1a260fc90d08e36d20917315f6f2d661"},{"version":"18.0.0","sha1":"13e159ee0204bc877b76091a5f2bd3f6be234753"},{"version":"18.1.0","sha1":"bbf42e3b37b8cbfa3ce4d30b09e0d77258c02cf9"}]},{"package":"play-services-stats","versions":[{"version":"15.0.0","sha1":"e0addd5494f140607e68d53b077ece31e78c25a9"},{"version":"15.0.1","sha1":"be55d3fe5e0fc91bff4807cee7c1283cd22a9c2d"},{"version":"16.0.1","sha1":"d5ca5898019c0913d163dbfeaa42b77b148716c"},{"version":"17.0.0","sha1":"5ed3441a1bcdb224b0fed0cc175257ed15174755"}]},{"package":"strict-version-matcher-plugin","versions":[{"version":"1.0.0","sha1":"6286ada829335cc495b43af4579e4403d9d454cb"},{"version":"1.0.1","sha1":"1e4e0daa2c4bbc5a81f0fbc343a7cca82dd12045"},{"version":"1.0.2","sha1":"7df73b847516082aae0d4cd2e32ae59447323199"},{"version":"1.0.3","sha1":"da218fc7abce75303be79d55bc7c2b79e63c1ada"},{"version":"1.1.0","sha1":"2f2740b60768460c48ee9e504332b68afec006d1"},{"version":"1.2.0","sha1":"ffb3e6af636830ecdf41cae429ae8672cc16a8ba"}]},{"package":"play-services-vision-image-label","versions":[{"version":"15.0.0","sha1":"5c6058c7083bc38e4a0406113f26d6ec75e983bd"},{"version":"16.2.0","sha1":"3546323225cf86dbcb9b121200d98d6f672771"},{"version":"17.0.2","sha1":"2ce482bc7183983bc8a83197bc20025975304dd8"},{"version":"18.0.0","sha1":"f30012614705f909b75919799dcf8cdf18e797d7"}]},{"package":"oss-licenses-plugin","versions":[{"version":"0.9.3","sha1":"d61378f93c8a0bd342e0b82870635c1566c3f12"},{"version":"0.9.4","sha1":"b06523162a17f54be5faa175725b67eb1521704d"},{"version":"0.9.5","sha1":"4ce2c5b8c12faa3772791d4acd819da3e0422411"}]},{"package":"play-services-measurement-sdk-api","versions":[{"version":"16.0.1","sha1":"f19d74a4ceebf68a96902dee1dbc04e37cf5760"},{"version":"16.0.2","sha1":"1c4d15d17b61149ef4338aab1ddf15334b6d933b"},{"version":"16.0.3","sha1":"c38e81955d20e6605e1b3cc9fcbdeb1fb26f73e0"},{"version":"16.0.4","sha1":"1012493bbebf856e1c5dbceb5aa5d43ee351dc3f"},{"version":"16.3.0","sha1":"b64b0806b255b06f1acf409ea50362f0c70d20b"},{"version":"16.4.0","sha1":"d99db32c0c464ca64be19b5b1463f7f259022624"},{"version":"16.5.0","sha1":"7085ec4651ca36a03890810c3d47781fdd31922c"},{"version":"17.0.0","sha1":"f524ab3ad654868c778bba8de30c6a36ad4ee3f5"}]},{"package":"play-services-measurement-api","versions":[{"version":"16.0.1","sha1":"498edc46c328c0aa80bdd0132ffd9bb57e0f1b79"},{"version":"16.0.2","sha1":"8cdcbb64fe1bec00863c401314e8d5d29cbe07b1"},{"version":"16.0.3","sha1":"bd32adf7e2c20a80c7e9a438ca674cd038a0a88d"},{"version":"16.0.4","sha1":"318abc31d36170793f270e5160d7a411b156a397"},{"version":"16.3.0","sha1":"a2b892641b06e5e4b9421157fa30c15aa7f69840"},{"version":"16.4.0","sha1":"a0fb7124619a101ea4a23063c74a636203903f7a"},{"version":"16.5.0","sha1":"5edae91fcc7979080f6991ea265679bae4b5a27b"},{"version":"17.0.0","sha1":"84d14b79e6c43bdfcbd9925844099230b3e5e743"}]},{"package":"play-services-measurement-sdk","versions":[{"version":"16.0.1","sha1":"bcf7a7367911d9e1eae2a3718d0a203350e39a32"},{"version":"16.4.0","sha1":"e94a909288dbfe0ffbee8d966d0291c9ddee964c"},{"version":"16.5.0","sha1":"93b34ebc75a23d76df09bf5f05e0817f4c50ee4a"},{"version":"17.0.0","sha1":"c1e84384acc701ba5956154378c832cb811530ad"}]},{"package":"play-services-afs-native","versions":[{"version":"16.0.0","sha1":"82c8c5b5a0125a3ba266f096a8930d66a6ffd010"},{"version":"17.0.0","sha1":"7091b87b109ef0b2cf92e1a5a0f44eb7cf0f5c59"}]},{"package":"play-services-measurement-impl","versions":[{"version":"16.4.0","sha1":"ad19027aac1c8270bdbbd1f56d99e64aa0743032"},{"version":"16.5.0","sha1":"5bbb2c4bb00fcae4423da0c5c6f24c10537b85ac"},{"version":"17.0.0","sha1":"4db890d78d517b689a30624adb60763712dcee8"}]},{"package":"play-services-cronet","versions":[{"version":"16.0.0","sha1":"401f1e8752433952af1df66d6337f18497af53c7"},{"version":"17.0.0","sha1":"e15df1122c7e76670ade11e91f53ca52ee660b56"}]}]},{"group":"com.google.gms","update_time":-1,"packages":[{"package":"oss-licenses","versions":[{"version":"0.9.0","sha1":"9f2570a2164fdda7027445613eaa1ea9b42da34a"},{"version":"0.9.1","sha1":"6ae659999a00ed4478bde13f79f5d365816865b2"},{"version":"0.9.2","sha1":"3839e8036257878b49a01a2bd3f9e30fe59bc0aa"}]},{"package":"google-services","versions":[{"version":"3.0.0","sha1":"ee538ce9cf4148485e43017c5542202b8c78ff8e"},{"version":"3.1.0","sha1":"166ae4e6acd03b7bbebe221ffa54bc039fece1b9"},{"version":"3.1.1","sha1":"aca432c928821bad3626ddd1b9164cd85a73641"},{"version":"3.1.2","sha1":"1fc9eb41c1937a7364345d13349682db7785546e"},{"version":"3.2.0","sha1":"202fd26a10eb8426e49bd1bdbb1ab093ea30bd76"},{"version":"3.2.1","sha1":"3119e2fe2069b7a69838e3e7b7c476ff0e847871"},{"version":"3.3.0","sha1":"48efdd03f47928e594370d426ef1c1fc5e172c19"},{"version":"3.3.1","sha1":"8138acb7149d74e93532d45e28776a34376c7633"},{"version":"4.0.0","sha1":"625ec4a9865e9e74fa5a12d7b0aea3652e904c6c"},{"version":"4.0.1","sha1":"a9fdd01c6f67fa7d12cc156a7a20a5086fbd1287"},{"version":"4.0.2","sha1":"4e44b4eb1caa8adeaa579af6e9203a46205051a0"},{"version":"4.1.0","sha1":"33036e6eac9c174a7aad841e93df3975852a5aef"},{"version":"4.2.0","sha1":"4b8cdb1daaed013c1523add39c8158d86e549d6c"},{"version":"4.3.0","sha1":"297ea733edbeeb149cea39b46d564403d665ea6f"}]}]},{"group":"android.arch.paging","update_time":-1,"packages":[{"package":"runtime","versions":[{"version":"1.0.0-alpha1","sha1":"6990acca0b445b7c7fff28487bc631ad5d30e1da"},{"version":"1.0.0-alpha2","sha1":"3ecbf672cedc26420875299c9ed8f8376b65eefd"},{"version":"1.0.0-alpha3","sha1":"8aa5fc8ecf19a45192338bb542602cba57dc80d"},{"version":"1.0.0-alpha4","sha1":"b45f2e546e782b5c6d723729b0a755754e493d3"},{"version":"1.0.0-alpha4-1","sha1":"b00d37faeac89344efec57f91c5473bd545681c0"},{"version":"1.0.0-alpha5","sha1":"c8ee598875e3f1ebfa4140e6448ab3eafb355d35"},{"version":"1.0.0-alpha6","sha1":"b5291fcd49bd704d5a53f56b2fa1506e796c4d6e"},{"version":"1.0.0-alpha7","sha1":"1f86375eb9660761ac221afec9177993b76024ad"},{"version":"1.0.0-beta1","sha1":"ecccebf14589fba56748b0a3dcac3db1eaee6274"},{"version":"1.0.0-rc1","sha1":"83e176dc78600f13c3df238cd5d3fe6545f97bbe"},{"version":"1.0.0","sha1":"1db3f9af17d810a46e186a2b9155ea7ff2806310"},{"version":"1.0.1","sha1":"69cd416d749d7bd787c8845a44480259efc81192"}]},{"package":"common","versions":[{"version":"1.0.0-alpha1","sha1":"cc6353e4cc15d635d124a49c9770c7b077db18d3"},{"version":"1.0.0-alpha2","sha1":"6b32fe6318d2109209ca781449036086569d5933"},{"version":"1.0.0-alpha3","sha1":"7853d336a4ca57137a40a4a53edc2f80041775e9"},{"version":"1.0.0-alpha4","sha1":"13dbf92e35635ac2f37028007a40ca087574a412"},{"version":"1.0.0-alpha4-1","sha1":"d30a42ed2acf40ae1f1469d0f4436493633b46ea"},{"version":"1.0.0-alpha5","sha1":"140ec67282be7695ec7390e218916704fa9d533b"},{"version":"1.0.0-alpha6","sha1":"9e8b1cf976277fb0b6c21c92c40084d866c1c65b"},{"version":"1.0.0-alpha7","sha1":"af72a8f827b0ac8523074bff680fd61709963416"},{"version":"1.0.0-beta1","sha1":"72dc7f412fb0701219ee3a10812632bde57173c4"},{"version":"1.0.0-rc1","sha1":"bda797f1b0b23a6ca2074282963fd099cbacd854"},{"version":"1.0.0","sha1":"9634a5f543f7cc5d97b62780fd1d5e9985fdb4b6"},{"version":"1.0.1","sha1":"f4fb10f7f5e73ce69d461ddde4ab222ef920607d"}]},{"package":"rxjava2","versions":[{"version":"1.0.0-alpha1","sha1":"9a34ee6d6ebe263ffe137a1a18401fdf600d582b"},{"version":"1.0.0-rc1","sha1":"50d616d4c67a583d7a7a4399ee210baa8424687c"},{"version":"1.0.1","sha1":"487f33e43a5b41b0579b80fe4eae2dd5a02936dc"}]}]},{"group":"com.crashlytics.sdk.android","update_time":-1,"packages":[{"package":"answers","versions":[{"version":"1.3.13","sha1":"83dc1dd439c7da04ce9705f18037fe7551ae06bc"},{"version":"1.4.0","sha1":"f45c369647b1ef1773f753f0236b309ea5ef4019"},{"version":"1.4.1","sha1":"b769b359d6bb1c0d7d69720853ad401255acb6a0"},{"version":"1.4.2","sha1":"336a2acb38eb083735c49f3e1010101936385803"},{"version":"1.4.3","sha1":"88b188bb596c976a6c7e7f8a8130798a71a8e5ae"},{"version":"1.4.4","sha1":"598c69e470744f7cbb9f824a94c45c752f50add7"},{"version":"1.4.5","sha1":"b296d3378e52fd9a0e2e1121347f1b85fa7eb361"},{"version":"1.4.6","sha1":"b56498872099be42f151bcdaadc8bee101fcb869"},{"version":"1.4.7","sha1":"5321e6b7e247c2d8bad7b4405525c8fde362d6fb"}]},{"package":"beta","versions":[{"version":"1.2.5","sha1":"5c2c8d71d19e8ac9feea57cc425e6474cc177e3d"},{"version":"1.2.6","sha1":"68ab3ab9e5308c3c7ab7d3880ab8826f17f2fa7a"},{"version":"1.2.7","sha1":"77dabbfbe24b68819632882a7b81da895b3cd540"},{"version":"1.2.8","sha1":"37121eee1dad7549ad793c28de1272dbb90ea1bb"},{"version":"1.2.9","sha1":"e6e79d5097bd94e565e69d80ce2867e3f93f4660"},{"version":"1.2.10","sha1":"8ccde9499a9f3ad02c0096ef3486db17731ce298"}]},{"package":"crashlytics-core","versions":[{"version":"2.3.17","sha1":"2c1c665ff848815f0793e3b65d2d3bd102f17da4"},{"version":"2.4.0","sha1":"886692c8eea3976b2850af462718767acd14f25f"},{"version":"2.4.1","sha1":"20c76550dfde5fe4396fd7506895d971de238839"},{"version":"2.5.0","sha1":"611db2754f8a1267f98ae33ab1e450d5e00e04b3"},{"version":"2.6.0","sha1":"adb0560c9417f32b9aecd01391bf84f63ed87623"},{"version":"2.6.1","sha1":"7e3ee437f389bec558453516a8ada39bb077cdf1"},{"version":"2.6.2","sha1":"aff76235543ec35f4c80de3ebf04112f1e209e1e"},{"version":"2.6.3","sha1":"2639b91181309072c7982f6d674d82563726de8"},{"version":"2.6.4","sha1":"edd299b819d169945f86f2c44253a6bb9fc2e59f"},{"version":"2.6.5","sha1":"aff0e7fa2580fd3a489137838a91a62dd0ea3ba2"},{"version":"2.6.6","sha1":"e7ccd7f77c2e13f9fb2ce7d6da2a75b0667d3590"},{"version":"2.6.7","sha1":"326dfe809d60281a8470fb98624ed2d4b3b99b06"},{"version":"2.6.8","sha1":"2069a189e58914727b3d6a2e9fe7e98b703c218e"},{"version":"2.7.0","sha1":"763b477b380f9e4a8df0552b4621deb16929040"}]},{"package":"crashlytics","versions":[{"version":"2.6.8","sha1":"abe471abe47f072e9951ec823fdadd0d053c1fc6"},{"version":"2.7.0","sha1":"1b086d238a192c21fbd7941b75c23990be9b6cc5"},{"version":"2.7.1","sha1":"5544368a2ac6fe05b2a5cc64389593627d95d7e2"},{"version":"2.8.0","sha1":"a524d31e7d725c9e7d0f9e1e07c7f76ff4c63168"},{"version":"2.9.0","sha1":"6c6e560dbaab4305447de3437a763d83962a3439"},{"version":"2.9.1","sha1":"2480c9b463b9e6f809fa68203c6cdaa7914cbe32"},{"version":"2.9.2","sha1":"d1f42d655967f230d5f483b943cae8586488844b"},{"version":"2.9.3","sha1":"f431631455a3e44a0b18fbc1954267f55b3ed74a"},{"version":"2.9.4","sha1":"fb64139f1fccfbb1a56521c5927dae9887a2350f"},{"version":"2.9.5","sha1":"12b6e8daa01ccde6c82739f0ff482160057601d1"},{"version":"2.9.6","sha1":"7088cc7d6950f059a7fb01a684251c1d161a0b11"},{"version":"2.9.7","sha1":"b50d2a6414a8b7c8cf9726c074286e821da11108"},{"version":"2.9.8","sha1":"2f9bcf4fdd1780a781cfe54ccf15e234593e10f5"},{"version":"2.9.9","sha1":"2cc0daced1a3cf87248ba19546073a1e7aebb78c"},{"version":"2.10.0","sha1":"dcc45c52a687ed1dc6d62c28e4e06617cdc57a52"},{"version":"2.10.1","sha1":"91d0a4b9b5e7b9e85870c63c74b17a4ba82efe1b"}]},{"package":"crashlytics-ndk","versions":[{"version":"2.0.0","sha1":"48d43076281c88264f71a729a00a5d2a69e85498"},{"version":"2.0.1","sha1":"3110c47d050e49de9be96affd8d1f1c3fa638c08"},{"version":"2.0.2","sha1":"fe135b7879201435ca18f4d3d27ba2df6f48105f"},{"version":"2.0.3","sha1":"e0dbb76fcbacf1de22f7efb2ec2ab2cdb43f9cf6"},{"version":"2.0.4","sha1":"ec1bd318f3ddf50a8c1f39877a149f77c88c468"},{"version":"2.0.5","sha1":"fc67e15413dc66ab1fb87d7717f899eebbd7462"},{"version":"2.1.0","sha1":"b3abe2bbc4607774e39c07a44ce0e75a93740089"}]}]},{"group":"io.fabric.sdk.android","update_time":-1,"packages":[{"package":"fabric","versions":[{"version":"1.3.17","sha1":"149e276a00a69b8ad14aa41283e0414bb7368631"},{"version":"1.4.0","sha1":"a18d8bfd2f7f1ba95ac70f8bef32ae5d8254af11"},{"version":"1.4.1","sha1":"a3ea5cd852f20ce41a521814b011b7937d032783"},{"version":"1.4.2","sha1":"30f4a34614ddd645182858b4d29059c25411c2fa"},{"version":"1.4.3","sha1":"36dbbff25bf9ad6fe6ef720254b9f58c151646e3"},{"version":"1.4.4","sha1":"68b9719b1d4e81f26491cbd71ea0f8ba82fc6fb5"},{"version":"1.4.5","sha1":"dc75e3ee10c412ecb304ae096a149e08b5233d96"},{"version":"1.4.6","sha1":"9ef544a9e6208100a5275e9247cae04aa88ee79d"},{"version":"1.4.7","sha1":"c7e2d02beb7950eb644ac8ece2579d8cbd4695e7"},{"version":"1.4.8","sha1":"3bf74ec4c468b0644c2d18c199b984dcaaa85681"}]}]},{"group":"android.arch.persistence","update_time":-1,"packages":[{"package":"db-framework","versions":[{"version":"1.0.0-beta1","sha1":"68f4b85ddf273afd3c30c87ee632c89d1bc6ad84"},{"version":"1.0.0-beta2","sha1":"49188ec9aaf37074043f0872b225fe34c2d41142"},{"version":"1.0.0-rc1","sha1":"8ad7d5f9ff7e758dc5a686f64ef286ee479ff886"},{"version":"1.0.0","sha1":"374d7c41b7b6d5e3c97dacffc34a0c98d70bccf4"},{"version":"1.1.0-alpha1","sha1":"2f0a29114dfd25db7112e118d7f4627288f5df8e"},{"version":"1.1.0-alpha2","sha1":"708ca348999107d2e1d4bc9a063a5d61c5e73a21"},{"version":"1.1.0-alpha3","sha1":"ea02b3cdde788f8b1abf5cfd46d532be93215242"},{"version":"1.1.0-beta1","sha1":"a7d0225d89d72f34a24dc81008ef9564a03a2a71"},{"version":"1.1.0-beta2","sha1":"d0a539510c3716814e1e7d58f4914f7f686980ce"},{"version":"1.1.0-beta3","sha1":"5f5a5b2787cfc2112a38c4f8db09a086417818a2"},{"version":"1.1.0-rc1","sha1":"d25d26b6d2f98dde44483ac543a4cd5131089774"},{"version":"1.1.0","sha1":"e541ece439f5c67b8af7547b12398e18980b08d4"},{"version":"1.1.1-rc1","sha1":"3344e9a8cce92cab358671502a2b35cbc941eecb"},{"version":"1.1.1","sha1":"66bfde755a7b87299f44389ccb533bdb8c3fdfd6"}]},{"package":"db","versions":[{"version":"1.0.0-beta1","sha1":"40940e38b064b17bc9fa6eb48be16ce7928bb7f"},{"version":"1.0.0-beta2","sha1":"78a6736c0103c5b190fdcd4b70419ef10f1b9bb3"},{"version":"1.0.0-rc1","sha1":"2ec105841d674970da680809ad89bffe39dc3d04"},{"version":"1.0.0","sha1":"a92f3e259de871bb60750b32f8b0c7e6c67a11b1"},{"version":"1.1.0-alpha1","sha1":"ab8503eceabb1f1faddbe9a3c621b9655a2230b5"},{"version":"1.1.0-alpha2","sha1":"2976f488d264ea18fb3e664e8eeb2e4e7307393b"},{"version":"1.1.0-alpha3","sha1":"fc5235f0caf7e99206cf21e1c67e5799fb256837"},{"version":"1.1.0-beta1","sha1":"1f5eb769e31acb5398ef57feb695c49768823e71"},{"version":"1.1.0-beta2","sha1":"7687c1843592b6bface66526c9ed5e8a61c86b2"},{"version":"1.1.0-beta3","sha1":"2fdea234019f4b3f09fa334b9c2bb6b347e03e44"},{"version":"1.1.0-rc1","sha1":"6df0119e2a26ba022bca50f4101c97a1a080cfea"},{"version":"1.1.0","sha1":"8539ec507fe8c1f47fa535642d96e08496068039"},{"version":"1.1.1-rc1","sha1":"9c6de5b474fb991055c8bac3c94598e128549851"},{"version":"1.1.1","sha1":"bfe3409ee71ce65348c904fa8e6224412b1d5e36"}]}]},{"group":"com.google.android.wearable","update_time":-1,"packages":[{"package":"wearable","versions":[{"version":"2.1.0","sha1":"830817563961bd61d5a2f008329e0968f97d9d95"},{"version":"2.2.0","sha1":"830817563961bd61d5a2f008329e0968f97d9d95"},{"version":"2.3.0","sha1":"c992787c16c39fdea0042bb10ff7873fee224c0a"},{"version":"2.4.0","sha1":"9088d02f75b3150f1d15f03d619636a5aefdda98"}]}]},{"group":"com.google.android.support","update_time":-1,"packages":[{"package":"wearable","versions":[{"version":"2.1.0","sha1":"fadc70465ec409642392a35a2e024b9356942175"},{"version":"2.2.0","sha1":"45a404038549cebc97fc0c6e66846f92fd04ae3"},{"version":"2.3.0","sha1":"4207c505a7d83472e336affd8ee64daf7270cb2b"},{"version":"2.4.0","sha1":"2dae2deb4b0eb4db9ba8f459143b6dbfb5bd9449"}]}]},{"group":"com.android.installreferrer","update_time":-1,"packages":[{"package":"installreferrer","versions":[{"version":"1.0","sha1":"7f4644e967eec4ad13290da5806ad1d4d6698994"}]}]},{"group":"com.google.ar","update_time":-1,"packages":[{"package":"core","versions":[{"version":"0.91.0","sha1":"da2caba973a305d880d979ab1cda6af1081747e"},{"version":"1.0.0","sha1":"5cc583045e311118adeed27cadab05650957006f"},{"version":"1.1.0","sha1":"c5b36916aedefc034d6edc22a3663c4ab2fd86a0"},{"version":"1.2.0","sha1":"ca5f5f4b28c6667ecaa2e7b97b365aa2ddeae0e1"},{"version":"1.2.1","sha1":"bd504c45dc808edb0bbfe27f0d11f9cc7cf97cc0"},{"version":"1.3.0","sha1":"5fdbc862ea13502dd0c762c33c15107f4da12520"},{"version":"1.4.0","sha1":"98b436f4cd1c729ca05fe68a9c7cfbfb1ede7b6e"},{"version":"1.5.0","sha1":"46906a446832ba7a83b0cb761d42eb7bdd10ab14"},{"version":"1.6.0","sha1":"ec619b7b67766d971cbf8b47d32cfec4290707b2"},{"version":"1.7.0","sha1":"7847f2d2128ae4fcfaa8b6add9c1ccf1c253093c"},{"version":"1.8.0","sha1":"42e825bd395f6da6a5af7b958bc838655c33a966"},{"version":"1.9.0","sha1":"495c4cb6899eac527515d52e699cc8d45fd15546"},{"version":"1.10.0","sha1":"1117ea0537a53d26351c779dc09a1d5c197a1be2"}]}]},{"group":"androidx.core","update_time":-1,"packages":[{"package":"core-ktx","versions":[{"version":"0.1","sha1":"bdcef6aee8523a3465fc8090f016b1f3dbcab01"},{"version":"0.2","sha1":"99630bd06b5102c7a303aa606e5e06c1d96c0af9"},{"version":"0.3","sha1":"ba8f6db2af289f98c4be36af06f976945649dbf0"},{"version":"1.0.0-alpha1","sha1":"afef1cecd5247f82376e0330a462376202349c05"},{"version":"1.0.0-alpha3","sha1":"1e03c050432541ba9a2d7cc8ebe02f0a49efab5"},{"version":"1.0.0-beta01","sha1":"3163a5c0b27651a0137e22704a4fa832eb99b593"},{"version":"1.0.0-rc01","sha1":"88b31d2d084cec07059ac435da343edab6f20b44"},{"version":"1.0.0-rc02","sha1":"eb3e26f6d401a467dce4680ca87d1b6766d1df2f"},{"version":"1.0.0","sha1":"ddf1b4d8852e0a8581ecd0efe3a510591abca93"},{"version":"1.0.1","sha1":"8cf00020ef4c695d6d4d5a04933175aab8f2560e"},{"version":"1.0.2","sha1":"4d1c9936c300a4169042468df8bb9d5f9b865d7"},{"version":"1.1.0-alpha02","sha1":"c0902e5d951c2cbd7a32d2632e6fb6d45ad20fbf"},{"version":"1.1.0-alpha03","sha1":"15c488ec689a908728c1eeda3fb79be1d3375a29"},{"version":"1.1.0-alpha04","sha1":"5c65d399af258c969d1a06e63a16ca5c557de464"},{"version":"1.1.0-alpha05","sha1":"b4bb66edb0c8c00bf46e1dcd2c07322f7ff433b1"},{"version":"1.1.0-beta01","sha1":"a28641f61e212170ebba49e3d8f21021abb3ca7"},{"version":"1.1.0-rc01","sha1":"ea7c0ec8c648fedd56772fb9162660f9d7d3b2c6"},{"version":"1.1.0-rc02","sha1":"7d74e4f3b2880c84c1d80249d693c3e64deb7317"},{"version":"1.2.0-alpha01","sha1":"1354ac2623c79b60fddddf9e2215270c2be4117"},{"version":"1.2.0-alpha02","sha1":"9e6268729f9be07228c40afdd619dfd8fc2aab95"}]},{"package":"core","versions":[{"version":"1.0.0-alpha1","sha1":"eb84e6835a67a615a37ee534955153712a98e951"},{"version":"1.0.0-alpha2","sha1":"fcf47ee25b4a6b2380dcb056a41d4159de09ac04"},{"version":"1.0.0-alpha3","sha1":"7ce8ff4ebee3fd4ec36fa823f484669526a3ee7a"},{"version":"1.0.0-beta01","sha1":"2fcc2091e5b87dc5b74bc035c059930761d51e33"},{"version":"1.0.0-rc01","sha1":"67e4bf90e61b2864efd18e0e1f208b719b393cd9"},{"version":"1.0.0-rc02","sha1":"60bb77059920f9aa1da6a917064a0672514de6a3"},{"version":"1.0.0","sha1":"48ad5ac253f1478cd7643ab4eef497a376098886"},{"version":"1.0.1","sha1":"263deba7f9c24bd0cefb93c0aaaf402cc50828ee"},{"version":"1.0.2","sha1":"12b23f32d65592a907e045370837e0cdd3019092"},{"version":"1.1.0-alpha01","sha1":"3cb42e91a971a065b3c4f232a5253847fff2ffca"},{"version":"1.1.0-alpha02","sha1":"56a4aa1ab122fde1b0cbb583ca397f0a009bebf2"},{"version":"1.1.0-alpha03","sha1":"6448845bf5f41ce53ade69d8bdce14018629fd82"},{"version":"1.1.0-alpha04","sha1":"7528107483a6f39f18abfb718b4f59fe2bca9fd8"},{"version":"1.1.0-alpha05","sha1":"d8d01efd0f75ed5a8531c5ea44ecb65a8b1947cb"},{"version":"1.1.0-beta01","sha1":"674708e39be24e46190d85cfdecdb60b8d1cf272"},{"version":"1.1.0-rc01","sha1":"1f464723e68cbe1a5adad0f761b163ba8ff8bc3c"},{"version":"1.1.0-rc02","sha1":"5c1a3394bacc74cb9c75ce2d4e5edb364e5c0da8"},{"version":"1.2.0-alpha01","sha1":"a64b7e70dfcdbeb519039a22b0c7038eead37f5b"},{"version":"1.2.0-alpha02","sha1":"35b29514ee026a82c670a3cc2948f9705859beed"}]},{"package":"core-role","versions":[{"version":"1.0.0-alpha01","sha1":"538b5da3b1fc0b4f29ce833102e53256fba63f2f"}]}]},{"group":"com.google.android.things","update_time":-1,"packages":[{"package":"androidthings","versions":[{"version":"0.6.1-devpreview","sha1":"82f741bbbfaf63577bb03a16bb2088fa78c29542"},{"version":"0.7-devpreview","sha1":"89bafc1e698409775d37f3679d52f6831e49267e"},{"version":"0.8-devpreview","sha1":"9f4bed867c8a6355f63c816d5e88146797c586f6"},{"version":"1.0","sha1":"556cbf8576275707500f486ca48f66e5a4af44eb"}]}]},{"group":"com.android.tools.build.jetifier","update_time":-1,"packages":[{"package":"jetifier-processor","versions":[{"version":"0.0.1","sha1":"46615f0d70478813b8eb7a6deae910153f919564"},{"version":"1.0.0-alpha02","sha1":"5149452aeb4e139676609ebb35a0e17c3c1261ca"},{"version":"1.0.0-alpha03","sha1":"68c113865afa7149f1f6c82ba10b8e3fb3a9a8a4"},{"version":"1.0.0-alpha04","sha1":"51ebec1a3d95730658ef5d77bcc1a69f18fab1ed"},{"version":"1.0.0-alpha05","sha1":"662eb50b1a549a4f9994c0c1ab38c8c42a5b5a3b"},{"version":"1.0.0-alpha06","sha1":"6ef88b4279780561bdbdc522c602dea8b366f96b"},{"version":"1.0.0-alpha07","sha1":"f66bc523dcefd37f71babb239feba7ee2e0b7306"},{"version":"1.0.0-alpha08","sha1":"132392ce35e6bf22bcd9182aa2eabf6d8019ffa9"},{"version":"1.0.0-alpha09","sha1":"54a82e28c926ad46216b296a7008292cfc0bb76f"},{"version":"1.0.0-alpha10","sha1":"6d494a04e0af27ecd4ec7a35c93759dd9c7443bc"},{"version":"1.0.0-beta01","sha1":"5087c3a6938eb4dfa9fc83ca6828ee5d0c327ceb"},{"version":"1.0.0-beta02","sha1":"624d187319c8dfd6bc8281b4afbab87c39bbb005"},{"version":"1.0.0-beta03","sha1":"99f0590e468afcc1e338404a99aa46f18a553a89"},{"version":"1.0.0-beta04","sha1":"8ed1fee7b1d03709dd8bd0224f21ef243e5c4a4c"},{"version":"1.0.0-beta05","sha1":"80532d7d1df1b7711ffde15ff97b8821d0879c2"}]},{"package":"jetifier-core","versions":[{"version":"0.0.1","sha1":"5b5ea95092f19b670dcf5a6ada4359e5203c79bf"},{"version":"1.0.0-alpha02","sha1":"756aab7addbc044eaf6a75755d2aec92015b5dfb"},{"version":"1.0.0-alpha03","sha1":"2be030a0aea0a569b4203ec16baeea1d88ae197a"},{"version":"1.0.0-alpha04","sha1":"9c7014965fca3aafbc28c9423cfdb28a1fde6476"},{"version":"1.0.0-alpha05","sha1":"da5f4fdd1349306b0f6c6edb404b155c01b6dc70"},{"version":"1.0.0-alpha06","sha1":"8405dbfbc20aabfc7a88165711cf4489c9d3ecea"},{"version":"1.0.0-alpha07","sha1":"ad0ffeea926db4037c6464bf0b9b0609b19dee1f"},{"version":"1.0.0-alpha08","sha1":"935fdb6b2436b501fe135636ac3aad21cd2fcfdb"},{"version":"1.0.0-alpha09","sha1":"c526841f915c5e890efc473674e54927b50acc1a"},{"version":"1.0.0-alpha10","sha1":"9eb7027c383061de12f93aae7a22cbeb97832d2a"},{"version":"1.0.0-beta01","sha1":"5ba95542fe42ac7457ae842b153062dff034f60f"},{"version":"1.0.0-beta02","sha1":"fa93fdb83bdfd51e80e09a808fb2515a7134d406"},{"version":"1.0.0-beta03","sha1":"15d34882acc4f5db13af2106c218175d6869e1b6"},{"version":"1.0.0-beta04","sha1":"92ae18564fc833685f30f5a20297dd32349a9f6e"},{"version":"1.0.0-beta05","sha1":"c2e0c420c6340744f0d0e61d2d1c4a12ee97c70d"}]}]},{"group":"tools.base.build-system.debug","update_time":-1,"packages":[{"package":"model","versions":[{"version":"unspecified","sha1":"37d519a450676850f8c8f7c24eb41092ac1c49f1"},{"version":"3.2.0-alpha10","sha1":"cd306a32258ccec385dbbdc08910755607a75c33"},{"version":"3.2.0-alpha11","sha1":"8451d30f489917c38e7da98a8bc7e38dc49afb2c"},{"version":"3.2.0-alpha12","sha1":"3b349665b6a40d446678eae846034849b01744b5"}]}]},{"group":"androidx.databinding","update_time":-1,"packages":[{"package":"baseLibrary","versions":[{"version":"3.2.0-alpha11","sha1":"c3e018c0735c8621cf0bea3dd190889d5b82180f"}]},{"package":"compiler","versions":[{"version":"3.2.0-alpha11","sha1":"3ba82bbbfd2e7b2a8288ef3d4d2cfc639ea05242"}]},{"package":"compilerCommon","versions":[{"version":"3.2.0-alpha11","sha1":"682edce917eeb75967935af6d4bfd6e9d0394450"}]},{"package":"adapters","versions":[{"version":"3.2.0-alpha11","sha1":"8a1aefeed6f239c4623b9a0837e28735bbe94dda"}]},{"package":"library","versions":[{"version":"3.2.0-alpha11","sha1":"977b6d43d53edd85ca14af5a7097996a06389635"}]},{"package":"databinding-runtime","versions":[{"version":"3.2.0-alpha12","sha1":"977b6d43d53edd85ca14af5a7097996a06389635"},{"version":"3.2.0-alpha13","sha1":"977b6d43d53edd85ca14af5a7097996a06389635"},{"version":"3.2.0-alpha14","sha1":"977b6d43d53edd85ca14af5a7097996a06389635"},{"version":"3.2.0-alpha15","sha1":"977b6d43d53edd85ca14af5a7097996a06389635"},{"version":"3.2.0-alpha16","sha1":"977b6d43d53edd85ca14af5a7097996a06389635"},{"version":"3.2.0-alpha17","sha1":"977b6d43d53edd85ca14af5a7097996a06389635"},{"version":"3.2.0-alpha18","sha1":"29cb879620b243e77c9ef2fdd55c3c0ad6c46a79"},{"version":"3.2.0-beta01","sha1":"b319476d5b649f5d160814752eade7b72f23e228"},{"version":"3.2.0-beta02","sha1":"5ad770308da9d6dbf4ea9fff1619eb1be0aa5a78"},{"version":"3.2.0-beta03","sha1":"973ec73ee243f8ef733b6fa244b2b86f7dad4cb4"},{"version":"3.2.0-beta04","sha1":"92ff954a1ca5adeb4129095c35897dbf071b6f30"},{"version":"3.2.0-beta05","sha1":"77f552b48064b86b561821657528db03613d0dff"},{"version":"3.2.0-rc01","sha1":"ae8486a6ee7e3c44cd692cdd8945404a9ce72f3f"},{"version":"3.2.0-rc02","sha1":"83354b0d9f93308b4f3a59cea9d7425f531d3d6e"},{"version":"3.2.0-rc03","sha1":"9a5407c6974783c2d4807be13fa15b33c8f2b577"},{"version":"3.2.0","sha1":"6c07e88081d794cc429afb088ed120794ee1f5e9"},{"version":"3.2.1","sha1":"3722af1d90b2314db183077873088f792992a483"},{"version":"3.3.0-alpha01","sha1":"49465b6f916b21464f15d1e253427308f60250dd"},{"version":"3.3.0-alpha02","sha1":"f82f1e0432b7e6e7dfda4c8298be33633695fe86"},{"version":"3.3.0-alpha03","sha1":"c1c1f7c3220a5ecaf064fb8de1111051685a7ae7"},{"version":"3.3.0-alpha04","sha1":"12e102c6ef094fbb9e90db5dc7c0e547700aaca0"},{"version":"3.3.0-alpha05","sha1":"a4e0a5475d54608fc2d56bf8e62d65666c4695c0"},{"version":"3.3.0-alpha06","sha1":"55f0bb393adb4f24172349ba0c0ba26f59841e55"},{"version":"3.3.0-alpha07","sha1":"cb8c6ecc0700a2674b383b95d2afcac280c92b90"},{"version":"3.3.0-alpha08","sha1":"fc3f3a3950aa273e1bc660b5539036d52f2eae97"},{"version":"3.3.0-alpha09","sha1":"bb4214615fe360d5072f8c64aab150f88addf46e"},{"version":"3.3.0-alpha10","sha1":"d4650de71e906a2f3a7f622f3e9623caeb7146e0"},{"version":"3.3.0-alpha11","sha1":"93cf1ecff70288c02ffbc7b5e1a24d00b9061e3c"},{"version":"3.3.0-alpha12","sha1":"4c4348f9f91c16d8279c661d8e6a62b5b816c0a8"},{"version":"3.3.0-alpha13","sha1":"e287b5d297022a0091a5017fa97fa0eb6d6cfeb4"},{"version":"3.3.0-beta01","sha1":"be96d4ed747d0df946d8b592b12559d8421e6ffd"},{"version":"3.3.0-beta02","sha1":"b346eefd6f101436fcfb4cc228a576e0719bb366"},{"version":"3.3.0-beta03","sha1":"4132c5dd7571d737ddace670209292f921eede89"},{"version":"3.3.0-beta04","sha1":"4f62b7a832ec16d5469532291a5e5012ae9eecc7"},{"version":"3.3.0-rc01","sha1":"adb515b00fae7aad578be4a9bb65877a584c4197"},{"version":"3.3.0-rc02","sha1":"6081d7ae9c1c9006a5e0f59b52ab42e1ded9e9f"},{"version":"3.3.0-rc03","sha1":"d71a12a9df6434b259b868ce9b879bb91dc6cdb9"},{"version":"3.3.0","sha1":"70cb96f0f8a35c03edddc33cd0a7a4719dde2221"},{"version":"3.3.1","sha1":"3aaef0e88b51789f35b5c071a12e68cfefac54e7"},{"version":"3.3.2","sha1":"237f8598370a49398309e29760046aaca3689ec4"},{"version":"3.4.0-alpha01","sha1":"9ff17930556ff6df59e8799940231bd92c4d4b5a"},{"version":"3.4.0-alpha02","sha1":"42762c5eab3111230585c6eb648b282ac3fd3f3f"},{"version":"3.4.0-alpha03","sha1":"f57034ec021e400f20dcb75337b19ffea5b78d0a"},{"version":"3.4.0-alpha04","sha1":"95a95f524ad4233f4910919ccc6dbd731a08151f"},{"version":"3.4.0-alpha05","sha1":"76b999c1f49035450afd80b87afd5fef2cb5e2f0"},{"version":"3.4.0-alpha06","sha1":"708f67ab02dcea67f48d26f6a7b84eaabbeb1f52"},{"version":"3.4.0-alpha07","sha1":"3549d0f12503adb3c5ad963bd011fc5d8184eeb6"},{"version":"3.4.0-alpha08","sha1":"ca01e48918af6fa5a38d591ddc8c5678736c2727"},{"version":"3.4.0-alpha09","sha1":"9045ba716621383bed7cf3abe461d30d95cf1e5d"},{"version":"3.4.0-alpha10","sha1":"c6dd9887330fb1bc052113daaba04e465ef90fef"},{"version":"3.4.0-beta01","sha1":"855e8f9de69df5c01f86e7ce9f8b6c9e33a6efe1"},{"version":"3.4.0-beta02","sha1":"e3638d6826d7de4f13c0b261436bf12f9be85278"},{"version":"3.4.0-beta03","sha1":"4719b2a5a066cc14f46c3ededeed5b8b6c927f2f"},{"version":"3.4.0-beta04","sha1":"c682b1c632a552cdea99156f4c502b120d80a214"},{"version":"3.4.0-beta05","sha1":"3c10fc0b7957845f6ed7b0f37201b522fb8c4e2f"},{"version":"3.4.0-rc01","sha1":"1fd4885ceae5f85b5eb14c399bf5ee31377536d9"},{"version":"3.4.0-rc02","sha1":"140c4f7296708bf27d0b01afa8cb943a29810bce"},{"version":"3.4.0-rc03","sha1":"926bde3ece0e9b81531e237cca3a49bd8788c714"},{"version":"3.4.0","sha1":"f83541b0422dda587f65e40fc29c459e23eb4d1f"},{"version":"3.4.1","sha1":"913acd9b818daf6a1706f56a9f0d1980f719bfcf"},{"version":"3.4.2","sha1":"1483d7681506094a8c9c146d3ba8f06fdf1fddbd"},{"version":"3.5.0-alpha01","sha1":"785f6ebf9fe94f7cc36b98e1058a83ea599936b1"},{"version":"3.5.0-alpha02","sha1":"5a69089f5581a0ec28bb73639fa4d9883378c7a6"},{"version":"3.5.0-alpha03","sha1":"1755d3e5cffc3323afc6f36bc13cc3375e9e9fbc"},{"version":"3.5.0-alpha04","sha1":"7f008563bd03901dba7c18e5e8a9a18523572b88"},{"version":"3.5.0-alpha05","sha1":"86fd38f67dfb9787b6b781f5ffb025215de14b18"},{"version":"3.5.0-alpha06","sha1":"a4682304c30412e5a2d1a72adcc6802d75a174ba"},{"version":"3.5.0-alpha07","sha1":"6377c1b4e8cf07589987f1c1d1be047fd4b2ca2d"},{"version":"3.5.0-alpha08","sha1":"3851fa5b25787d2f9181a29dc7be53656b555a8e"},{"version":"3.5.0-alpha09","sha1":"bf284a0a080aaf7413faded77a668bc0e7ef21d4"},{"version":"3.5.0-alpha10","sha1":"f9f5475b55a68c428d6b9d3f0b9380db54e0be2b"},{"version":"3.5.0-alpha11","sha1":"5b7ad5aa907da1c15d3de1db861d240efd00f09f"},{"version":"3.5.0-alpha12","sha1":"88c06b699eaa48df7ccbcab0abbc94c4df3f2395"},{"version":"3.5.0-alpha13","sha1":"f3aaf89f01233c6571bc07d93d3a89c983814b0f"},{"version":"3.5.0-beta01","sha1":"90cbcb5ce3095476f0ee268cdc812e32bce87eae"},{"version":"3.5.0-beta02","sha1":"bc47a479a46eab93d7d8307202f1255579620f21"},{"version":"3.5.0-beta03","sha1":"16aae919d6fde8d9ba0b0a60e488ae26dd092843"},{"version":"3.5.0-beta04","sha1":"6688fa42576c47c5ffa7b7a1582b1888017210de"},{"version":"3.5.0-beta05","sha1":"a9bd7fb00475b6599882256f66990131427827c6"},{"version":"3.6.0-alpha01","sha1":"8cba1f467afc47f067421ee9f193dc641ff50c20"},{"version":"3.6.0-alpha02","sha1":"a033aefd2114fab76829cb6a3d75ecb2371afcd4"},{"version":"3.6.0-alpha03","sha1":"622cfc8f281151cb720e5739b2af4f1319af91a9"},{"version":"3.6.0-alpha04","sha1":"f54a569e475639e35e349c20b4825d09ea0f5bdd"}]},{"package":"databinding-adapters","versions":[{"version":"3.2.0-alpha12","sha1":"b9858633b78d74e54f7c8db7598a572be1482d12"},{"version":"3.2.0-alpha13","sha1":"b9858633b78d74e54f7c8db7598a572be1482d12"},{"version":"3.2.0-alpha14","sha1":"b9858633b78d74e54f7c8db7598a572be1482d12"},{"version":"3.2.0-alpha15","sha1":"b9858633b78d74e54f7c8db7598a572be1482d12"},{"version":"3.2.0-alpha16","sha1":"b9858633b78d74e54f7c8db7598a572be1482d12"},{"version":"3.2.0-alpha17","sha1":"ff8c6db798b5632c21a259d53c456b9f1849022"},{"version":"3.2.0-alpha18","sha1":"28330760ac7fdd689568a02028992166f67be369"},{"version":"3.2.0-beta01","sha1":"514d1b536a22999218fc5521b9dae7619fe0ced"},{"version":"3.2.0-beta02","sha1":"9020e5072d14aa5794cf06d30ce69d0c37bbab2a"},{"version":"3.2.0-beta03","sha1":"1e2f64eb063408f6d020f1bd906f67d4189ed5a0"},{"version":"3.2.0-beta04","sha1":"63a5f5bdec6ed57004c02a6213523c8534356d63"},{"version":"3.2.0-beta05","sha1":"deae168e53fd2d7e84ad210e7ce4648b2eaea87b"},{"version":"3.2.0-rc01","sha1":"a81730c27f6ef46e3f309d7f6855a4202022ae6d"},{"version":"3.2.0-rc02","sha1":"f9452524f7e24cd99aac43214a70b5258b2b0c5f"},{"version":"3.2.0-rc03","sha1":"3dd30ec806398539bcccee3095d93a724a52b63f"},{"version":"3.2.0","sha1":"f769729f573d94fb79d988af43bf031d060a209b"},{"version":"3.2.1","sha1":"fa5b237405d8658e38a5b17e462bf668ace19852"},{"version":"3.3.0-alpha01","sha1":"f6d9073d648fbb9896f13c5b31686e7dae55339c"},{"version":"3.3.0-alpha02","sha1":"38f05f3f037e92c3aba2eda253b2b05070900b8b"},{"version":"3.3.0-alpha03","sha1":"4550e145fa91c60223fc21f8e969914009a0affd"},{"version":"3.3.0-alpha04","sha1":"26f5a6890e667573bfdfe00e0e8fc5326b81804e"},{"version":"3.3.0-alpha05","sha1":"4807c9105dff8232316ec8988dd18382d99d4fcd"},{"version":"3.3.0-alpha06","sha1":"aa54ae35cc6e37c789cfd66913b26113ed12406d"},{"version":"3.3.0-alpha07","sha1":"341cc0e19565575596b0b49b62acb45606770e58"},{"version":"3.3.0-alpha08","sha1":"45ddb345859e093fad42537f22c24a194733a27b"},{"version":"3.3.0-alpha09","sha1":"5067831a7317157b2e5203db6e778b7855b332ab"},{"version":"3.3.0-alpha10","sha1":"912f4dde15f852a030389915c84d3d95cce9c771"},{"version":"3.3.0-alpha11","sha1":"6f702fa79dbaa968ddf017a7b3d86752a21d5ec"},{"version":"3.3.0-alpha12","sha1":"b62daa7c0cb711617734c1054944bf9cc1aa94ae"},{"version":"3.3.0-alpha13","sha1":"8050220c88b3e5a3ea453b333c7b6d5f04896f30"},{"version":"3.3.0-beta01","sha1":"2545a8fd67df3e08edce9cfd5a5a8ca50db19af2"},{"version":"3.3.0-beta02","sha1":"d0f40fe5b0b439b29ee29c41d0b095b28d539e96"},{"version":"3.3.0-beta03","sha1":"c211af07c80b159d4a579ca67cbe308a98803df0"},{"version":"3.3.0-beta04","sha1":"90b7526b88fad2ee335d0f91bcab1ddb03d2e111"},{"version":"3.3.0-rc01","sha1":"e3cac3b2e1ee4b7f7d0d9e6113e95717fb4cce1"},{"version":"3.3.0-rc02","sha1":"91961128b4b99bb4920dc50d5d53c0555d3ac4e3"},{"version":"3.3.0-rc03","sha1":"340e7aca73769731c262da6d65c02e0f2902c598"},{"version":"3.3.0","sha1":"778c910911ef19142d9925f9688b217da8737376"},{"version":"3.3.1","sha1":"f535631e82c83da5327c8362df333f28abcb49d3"},{"version":"3.3.2","sha1":"9cf8ac1e1adc571807db9e8552e4e4a9c9e23cee"},{"version":"3.4.0-alpha01","sha1":"1596ea33eae477ad7d87face52fc9535dbebdfd7"},{"version":"3.4.0-alpha02","sha1":"74f8bb47c7248bafb107fa5ec2259b0efce7f645"},{"version":"3.4.0-alpha03","sha1":"ad28893ee57e2a7f1e54ec470de6be5c977509ea"},{"version":"3.4.0-alpha04","sha1":"19406f0503c0346fa584cc584196857f7153e9d4"},{"version":"3.4.0-alpha05","sha1":"c57fb826ec99c6ed3c95052e852c9af2eab47dba"},{"version":"3.4.0-alpha06","sha1":"8852c820839d2a2b2c36872c310d65c0e49529ad"},{"version":"3.4.0-alpha07","sha1":"25226c5c23c5c9044ae2df71daa2b5f7babbe9a0"},{"version":"3.4.0-alpha08","sha1":"2c3c2acd2a5d4ae7a790f3d5590f396335006ab3"},{"version":"3.4.0-alpha09","sha1":"37f7028883bd759679395c0acde260b3faee23fc"},{"version":"3.4.0-alpha10","sha1":"e979959185e86ddc1a65801ee8b6523c1f68cb05"},{"version":"3.4.0-beta01","sha1":"b4ac1202a4fb5879d763b076fbae1cbf07400401"},{"version":"3.4.0-beta02","sha1":"7db4e6a5056e21596478f20c11e9a4d2bd26fe35"},{"version":"3.4.0-beta03","sha1":"2cd4550d125a102ce5b49e27242a6725ed33d1d6"},{"version":"3.4.0-beta04","sha1":"a5367e20554da1199898af5d961b85a00ba4ae76"},{"version":"3.4.0-beta05","sha1":"4769e9a696fbd31f09b82fc0474ef5f472326862"},{"version":"3.4.0-rc01","sha1":"ccc8ccb9383cbe4a96731b22d4fe3e4d854c119e"},{"version":"3.4.0-rc02","sha1":"aa7600b57e93a9cd68abc313a5f507ee13542f19"},{"version":"3.4.0-rc03","sha1":"8a3d72ffa1df1a2f9a3c0cae856de6c8f736aa84"},{"version":"3.4.0","sha1":"11922da3b07e8a00b35d1be16066810f8074b89"},{"version":"3.4.1","sha1":"2cfc3a714ab7b43a0b5bbcac8e532c8fce0feb04"},{"version":"3.4.2","sha1":"50a42b0fdb1599f93a1fbe1365181be7970efdb8"},{"version":"3.5.0-alpha01","sha1":"d22ee2699c3097d802592d8ccb1fc6ee180b9660"},{"version":"3.5.0-alpha02","sha1":"64549ee598751a6e45c4456971bd82c8df405250"},{"version":"3.5.0-alpha03","sha1":"bedbbe954f070f5da3e687272c55e4d40e645652"},{"version":"3.5.0-alpha04","sha1":"907efdac924c25fdc7dbb08501c6fe7e9c5e4665"},{"version":"3.5.0-alpha05","sha1":"7614b1097fa8d736952613f5fb031e28f5d94877"},{"version":"3.5.0-alpha06","sha1":"b7048b1c8a84b378c4e1f16ae6dfcd17e24082f8"},{"version":"3.5.0-alpha07","sha1":"1946505e4896de4c22d97a2b05630d38be584add"},{"version":"3.5.0-alpha08","sha1":"86f15d6ed654ab11af62bda19687c47e086afd15"},{"version":"3.5.0-alpha09","sha1":"1df0c29264ce1b87fb47c09a0a162448779a9d42"},{"version":"3.5.0-alpha10","sha1":"dcd760e104a54c826c674b6b2bb8800b3a246c43"},{"version":"3.5.0-alpha11","sha1":"121bd7cea1fcc1add294847d162f863e04d2f168"},{"version":"3.5.0-alpha12","sha1":"ad449e178f4a39d89c522245fea56ed97510dd39"},{"version":"3.5.0-alpha13","sha1":"29337fb46be5ba2a14c56c996099ed4bce8fdbb3"},{"version":"3.5.0-beta01","sha1":"ac1cb28f5eca0373aaf277436505edf133e86e6b"},{"version":"3.5.0-beta02","sha1":"bc30030c58a5cee3407b7c7d34fbb625b7e5647a"},{"version":"3.5.0-beta03","sha1":"7cfa97129756ed5a947a6eee85c1dc1c751d68bf"},{"version":"3.5.0-beta04","sha1":"f9b9f9b540752e7476e7ae4d44b3756407d931fb"},{"version":"3.5.0-beta05","sha1":"cd225eebe6be34e4fa7980a75e4e88995b8b9e90"},{"version":"3.6.0-alpha01","sha1":"d44f20bb535bdfe5435a461862d89bc9e82249df"},{"version":"3.6.0-alpha02","sha1":"9750f005caf1d0971ce6e9efbaf930b7860b8a46"},{"version":"3.6.0-alpha03","sha1":"5b99ba566a4585ad4a958b9c1386988c56491f25"},{"version":"3.6.0-alpha04","sha1":"d5c8a768f601dc9cc5b9bf72f1c2dd29514b62ad"}]},{"package":"databinding-compiler-common","versions":[{"version":"3.2.0-alpha12","sha1":"dc9860d0c3f5edecf086b2aa4048ac8ffcd3922b"},{"version":"3.2.0-alpha13","sha1":"9ba6204ea6c1063e7d6993bf95163849a71b3ce5"},{"version":"3.2.0-alpha14","sha1":"26196c4e9d91f8f4de85166807dbe17075a2c4b2"},{"version":"3.2.0-alpha15","sha1":"6a082b22a54844cad67cac1a2432b5ffaa1d281c"},{"version":"3.2.0-alpha16","sha1":"7aa4a9fc725adaa73a3125b4ed832868dfc24374"},{"version":"3.2.0-alpha17","sha1":"5d69056a417c299fd26ed4b04eb6b336344bc512"},{"version":"3.2.0-alpha18","sha1":"db12c05db6aeda7c021cdfdbc2c8a6f3d1edf761"},{"version":"3.2.0-beta01","sha1":"2d44a6234065297f9a87945fcd34e69586fb55d1"},{"version":"3.2.0-beta02","sha1":"703560e740b9e68d5bd626a40dca19afe82cbc60"},{"version":"3.2.0-beta03","sha1":"56c9b3e2886e8e495da471bb63f96f618915aa71"},{"version":"3.2.0-beta04","sha1":"ff35ae3806536083d63a3c8fae485d43429b8c6c"},{"version":"3.2.0-beta05","sha1":"3dc7ad12da5f427f3f6624c519548864c9aadcf5"},{"version":"3.2.0-rc01","sha1":"cb82780f2c5da116d3f05c875a1e86ff84fbfe49"},{"version":"3.2.0-rc02","sha1":"793cb4b7d1101fb09255bae4ebc3b3b7003a2e14"},{"version":"3.2.0-rc03","sha1":"7509d057fa2c58ed056b28d7c54bdb5f43acb29"},{"version":"3.2.0","sha1":"b73212517c2f1c275a38dc9623e78ed020d7d887"},{"version":"3.2.1","sha1":"dee402009966fb2d3bb03e3186f3f16b9c503c86"},{"version":"3.3.0-alpha01","sha1":"b09019f8b37fb8cb9bc204ac4824564791fecb6b"},{"version":"3.3.0-alpha02","sha1":"48999b0ffefc60d5f82835e5d48ff9d61a034d7f"},{"version":"3.3.0-alpha03","sha1":"97ce85d013c7680d0645a150df7d655113deeed5"},{"version":"3.3.0-alpha04","sha1":"a330f77b93cc53b014146850338da8825547d3fc"},{"version":"3.3.0-alpha05","sha1":"c928b086cf23e5e05133b2d3df7ebdeeadc811de"},{"version":"3.3.0-alpha06","sha1":"4f428e4b059974db7bb79a5b1372e356b3b47be"},{"version":"3.3.0-alpha07","sha1":"5b3d4618968ad5967e24c393123628f9a87c0e5e"},{"version":"3.3.0-alpha08","sha1":"c90512b87d894c659d8eea94432219ea98bc3321"},{"version":"3.3.0-alpha09","sha1":"cad51b09e1814084a52075af444dfd495dd9b664"},{"version":"3.3.0-alpha10","sha1":"b8d194edec5230a0616d6a857da7e845e935bffe"},{"version":"3.3.0-alpha11","sha1":"52d030268300ca1761545e05692acc002ad1d74c"},{"version":"3.3.0-alpha12","sha1":"129552d3c7e3a29c596b5e24721f045ef559db7"},{"version":"3.3.0-alpha13","sha1":"8b851443c3f00b2aac0a1ff859be6074cc81d453"},{"version":"3.3.0-beta01","sha1":"2316cfc0c5655711415f8b67c011b00c154fd980"},{"version":"3.3.0-beta02","sha1":"b2bb77c1a9eb67e2175283fb1f8cc08dcd10da0e"},{"version":"3.3.0-beta03","sha1":"8a47aeaf0bb596bc3858bce8f10d1813f735ea3d"},{"version":"3.3.0-beta04","sha1":"e00e9bb8ea3e9825299eb428d1c5b047c1a061d4"},{"version":"3.3.0-rc01","sha1":"c621c3f4b9c0bc199551d179d2e5c7df8e47182"},{"version":"3.3.0-rc02","sha1":"25ecc5471c189dc13eaccd1f8e886f65a20c22a"},{"version":"3.3.0-rc03","sha1":"7b67c28414dc942bb63bdb27618d3af4b6882661"},{"version":"3.3.0","sha1":"d940cafe799b457249e57fbc87eb9b7e656c1fc7"},{"version":"3.3.1","sha1":"e96aa159e1ed5c5a3461d15adfc42ef5b1f68b59"},{"version":"3.3.2","sha1":"62da33e20ba59e879f3cd60ed0d8353f1dbddeba"},{"version":"3.4.0-alpha01","sha1":"4c16c39c0df0074881819c31ccca8a3cc7cd8cef"},{"version":"3.4.0-alpha02","sha1":"60872236c5346ba12b5b272e14e13b9e9ce9dc86"},{"version":"3.4.0-alpha03","sha1":"a45003b40ee685a64755dde5cb47aa24cefd4429"},{"version":"3.4.0-alpha04","sha1":"5eaa8f131254fc29cdd04a5c6fdb1c5ab7c8d3c6"},{"version":"3.4.0-alpha05","sha1":"9e946fdcc21ffd8ca8bdac1fa79798d2a60c408f"},{"version":"3.4.0-alpha06","sha1":"79c9a6cb8e5f1852f13e6c606c55b1e1963f7029"},{"version":"3.4.0-alpha07","sha1":"3acf83b7a8d5b703a8de03316475b474ca967395"},{"version":"3.4.0-alpha08","sha1":"c36045434061fefcedc6885e047335c8d4027844"},{"version":"3.4.0-alpha09","sha1":"b52f8338d307d17f97adefd5f4a3c23ea2f8f2b2"},{"version":"3.4.0-alpha10","sha1":"fc2b3d80646bc4b41161781287d6de2abceae0b6"},{"version":"3.4.0-beta01","sha1":"8e5f344892fa8d6c0d7b906d34fee3e7714ef1af"},{"version":"3.4.0-beta02","sha1":"72dfa2a39b73585e1ada6d6b7c49ff731ac03fc8"},{"version":"3.4.0-beta03","sha1":"81112494f4a17d8022a079c0d4fb72b62154fd10"},{"version":"3.4.0-beta04","sha1":"40feb0db73e38e7dd3a27d3893acb34c71b65495"},{"version":"3.4.0-beta05","sha1":"425e40e0314c20a8f974d64f10f1f12ec97d1fd1"},{"version":"3.4.0-rc01","sha1":"a95e0624bf8fcdd0167bf3a5ce18b69fcbae5898"},{"version":"3.4.0-rc02","sha1":"425052818eaf2179c137398b0a1732b49574369d"},{"version":"3.4.0-rc03","sha1":"e5bed3c9d15402d8bf21308a1d0c04b6eedf9cda"},{"version":"3.4.0","sha1":"9a080792ea0c78422230c02bc8f457e7a6c19f17"},{"version":"3.4.1","sha1":"f18b37dd0306f680e4ebec6f508beb66bc10f5f8"},{"version":"3.4.2","sha1":"d4654f1040856d9e1f95d3bc9bafe5cd02d8b42d"},{"version":"3.5.0-alpha01","sha1":"e0eaf86334144096e89bf228991da62a1ae575e9"},{"version":"3.5.0-alpha02","sha1":"d7b4f0c81d995d49ccf02f32df799814c9643987"},{"version":"3.5.0-alpha03","sha1":"594221c814d9e7f7794f02413abd9b5c5aa547dc"},{"version":"3.5.0-alpha04","sha1":"629434959b678a6df8dadc4da77112881e6ec499"},{"version":"3.5.0-alpha05","sha1":"648b3a7b8d24e26af836b2d96ac96a82e1dfec0b"},{"version":"3.5.0-alpha06","sha1":"2096567f1bae366484a3e16d715cfdfb2cb145a3"},{"version":"3.5.0-alpha07","sha1":"3a60ca1ef50830cbf191ccf3aac2fb8986f296dd"},{"version":"3.5.0-alpha08","sha1":"92d1e59b932dd6ff2b026e06a8340b72acfc093"},{"version":"3.5.0-alpha09","sha1":"bb972de3d43d951773e4d1d5ac26858a7e131b50"},{"version":"3.5.0-alpha10","sha1":"5c55f7ba20e8efcc9ce9fafca373b7c4fe2990e"},{"version":"3.5.0-alpha11","sha1":"c46c029bb11b175d34455f2c25ed42ec99ed413e"},{"version":"3.5.0-alpha12","sha1":"9b8224210f1252cc773ac1164923958eedc5f8f7"},{"version":"3.5.0-alpha13","sha1":"77e0652af754f79cf31535150cd6193d9a15b9e5"},{"version":"3.5.0-beta01","sha1":"33f8345ff1be1427b5a0a6500bbf94e3b28dd975"},{"version":"3.5.0-beta02","sha1":"76b0f1a80d5bbfbc39c6b32bd1a1f9efcae8ed7d"},{"version":"3.5.0-beta03","sha1":"1532b91573a2391804b154f184fc6c56f458b99e"},{"version":"3.5.0-beta04","sha1":"705f0b404e1b76437ce9033b282035962f2c547f"},{"version":"3.5.0-beta05","sha1":"95ee13088124e8e378b624f71842475746cc6298"},{"version":"3.6.0-alpha01","sha1":"e5595f410f9e65322c81ef68942693c04b2cc7cf"},{"version":"3.6.0-alpha02","sha1":"7bba80c9d8440aff07649e276061b9c4cff0fdbd"},{"version":"3.6.0-alpha03","sha1":"8648c71604acea586540990549c2d36852668889"},{"version":"3.6.0-alpha04","sha1":"c91db3967622f350716317484f3bee390ecdeb3c"}]},{"package":"databinding-compiler","versions":[{"version":"3.2.0-alpha12","sha1":"3f6994f4374ee622aac7cb67093e6479fcda1790"},{"version":"3.2.0-alpha13","sha1":"c7b7e9ede290c1cf9a4844679b7fc8463454fd1c"},{"version":"3.2.0-alpha14","sha1":"855526b7a626949e72e8135377188bbeac7fedb6"},{"version":"3.2.0-alpha15","sha1":"368cfb96671b84cdfcc77d1a8a220126ff23686a"},{"version":"3.2.0-alpha16","sha1":"8c8dadf864d8dc0ffebf2bfaf96f834208b3e040"},{"version":"3.2.0-alpha17","sha1":"b6c8d3bbb779252731f08c412325bb08595ac847"},{"version":"3.2.0-alpha18","sha1":"e095f166259d56d1dbafa0bbcf365e446bf054dd"},{"version":"3.2.0-beta01","sha1":"b67bb73f12dd013ac0fecdfc95b5649308f66ed4"},{"version":"3.2.0-beta02","sha1":"886326e6bdb6108de9f44752eb8f318603cdb6da"},{"version":"3.2.0-beta03","sha1":"e95cf1a6204f2f2727d8d2d44b2ccc3c2f3beac5"},{"version":"3.2.0-beta04","sha1":"1c6c07b9dc930181af6f5110ab69422a70c4d703"},{"version":"3.2.0-beta05","sha1":"939cb390f7c29a6fb178323df99e742f6fe8b1c1"},{"version":"3.2.0-rc01","sha1":"51c8a5b5660adb89e74da82796710df348e4d263"},{"version":"3.2.0-rc02","sha1":"971df8c365b4502f3fde2997c45febab4b385a76"},{"version":"3.2.0-rc03","sha1":"1c66b75a79d125e62a3f8b47eccc88483b8568e4"},{"version":"3.2.0","sha1":"765e0039a232d79ab0251a27665184f5546a5cdd"},{"version":"3.2.1","sha1":"7f0675f8d406183b9e35eea3faaef3f65f3d3d58"},{"version":"3.3.0-alpha01","sha1":"a6ca0a00298b93978252e9e7949d320c01bf73e3"},{"version":"3.3.0-alpha02","sha1":"f8b70b21ed18cc0b9fda7a177a7290dc6bf2e52b"},{"version":"3.3.0-alpha03","sha1":"3a664d555b3b72fd918667db57daccea19d2b820"},{"version":"3.3.0-alpha04","sha1":"107ba0e8fa92da22e06656bc1108619e1d2d1264"},{"version":"3.3.0-alpha05","sha1":"60abd90c988f50054e6c3b1cd4a53cd85cd82db1"},{"version":"3.3.0-alpha06","sha1":"f8bbcf3f2d012dee5ffbe5d21e0a3df06089c0b"},{"version":"3.3.0-alpha07","sha1":"f0057f6963d442b8b54127665fa6ea119fbe1c55"},{"version":"3.3.0-alpha08","sha1":"aac67253914008e9c882b345bbaca47061c124ed"},{"version":"3.3.0-alpha09","sha1":"403dbfd0c8a980a85626584b914e1d4bd6907557"},{"version":"3.3.0-alpha10","sha1":"d58d128759158692c9565c6bc7d08be26b61c5d1"},{"version":"3.3.0-alpha11","sha1":"b68e8095785d4ecbd6ae224887e9071d660cf641"},{"version":"3.3.0-alpha12","sha1":"d260e87e29431b8c984aa6c2abc72725945f4b9c"},{"version":"3.3.0-alpha13","sha1":"ae0565c4112e9d2eaf70299ddcb190270ca60b45"},{"version":"3.3.0-beta01","sha1":"2ee30a9275ae776d953c2e48107517837e49a8b6"},{"version":"3.3.0-beta02","sha1":"3d93b2448ac690cb78e4b8bee06a3b96678be599"},{"version":"3.3.0-beta03","sha1":"5edc3a43c237f8b7509c6a7bf6d8cfb2700be06"},{"version":"3.3.0-beta04","sha1":"d5db7415f58528be660aab86f358d6581cecf356"},{"version":"3.3.0-rc01","sha1":"ca2c8baabdfe778f72d72ff4d9135e436b08e326"},{"version":"3.3.0-rc02","sha1":"fa481abfaac22f326c2374a37f3d46870699a866"},{"version":"3.3.0-rc03","sha1":"ccd335895220ee9cbcf861c06eb53fad412aa947"},{"version":"3.3.0","sha1":"25f99ed340a7437bcad69b5464632cfcbddac420"},{"version":"3.3.1","sha1":"942682edbb5a02f2aea51432ac10c2e0e9ae6495"},{"version":"3.3.2","sha1":"dd3411d9e93dada227e8f6a8a13c851cfa9532b4"},{"version":"3.4.0-alpha01","sha1":"72db11deb6980caac095725596ef523891d0c706"},{"version":"3.4.0-alpha02","sha1":"38fd99ee99e1837ff949c180004e27484396b52c"},{"version":"3.4.0-alpha03","sha1":"ffaee7bcf856618a9250000556b3f2b15cfd9815"},{"version":"3.4.0-alpha04","sha1":"2428aba8ab93b7719108240a9d0c314757d90ca1"},{"version":"3.4.0-alpha05","sha1":"6caa6a00575ba94ef0d812c1eacddc57aef5b272"},{"version":"3.4.0-alpha06","sha1":"55e7bf077f75369930b74477e5a068527b411a6f"},{"version":"3.4.0-alpha07","sha1":"bf30f1644d828749ec7b5c7c1edcac2a6294a255"},{"version":"3.4.0-alpha08","sha1":"fb67fac5c8a10e0769ea6fb7c120cf32d4593dfe"},{"version":"3.4.0-alpha09","sha1":"48690cd17348b481127334d0e8911c675963d22"},{"version":"3.4.0-alpha10","sha1":"4b558eb2f986c03b8c10a0ed2a0415c00c9da007"},{"version":"3.4.0-beta01","sha1":"48948f4af291a126c21f6ce2abb84e66f1edbcb4"},{"version":"3.4.0-beta02","sha1":"1d44c4fbe03ae3acc5c95d460e9a10f80b1a2ad7"},{"version":"3.4.0-beta03","sha1":"8349a787bade931135362b77b88bb07e3faae230"},{"version":"3.4.0-beta04","sha1":"25c9627f944ff9bd38d3f2eefe97ee6d07493906"},{"version":"3.4.0-beta05","sha1":"e8c7a39818dd797a5255862cb4ca78456f4473b9"},{"version":"3.4.0-rc01","sha1":"c6de20826db874ebe6bfb59d4bc3cc1b12528861"},{"version":"3.4.0-rc02","sha1":"471c97836c50906939e0612a75aaafa075fd7d6c"},{"version":"3.4.0-rc03","sha1":"864a7063aed102b640e64ec969f4bd7c4abecbce"},{"version":"3.4.0","sha1":"8dc37ce7cc8cdf19408f37f2bbe6d68581cf5ca2"},{"version":"3.4.1","sha1":"487f87d30950cb6af23589405d652dc363b84989"},{"version":"3.4.2","sha1":"7f6a078deeef2a6110f4eb3d779f5eacc508bd48"},{"version":"3.5.0-alpha01","sha1":"2e7448e6e28a545b9d71fbc95c9bdf9b7ea30b90"},{"version":"3.5.0-alpha02","sha1":"7d836d789b625872d6ffe2f7fe86ce6ca731dcda"},{"version":"3.5.0-alpha03","sha1":"a397d7166a956685eb8eae3ca52042bb524054be"},{"version":"3.5.0-alpha04","sha1":"cdbb0f166d255018dc39193c40382c8a7a1f392b"},{"version":"3.5.0-alpha05","sha1":"bff7e1abf23d646128da7c5d91133e043b67747a"},{"version":"3.5.0-alpha06","sha1":"7c5aa40f615ffc0bff2fd243e33a11e8d57646a8"},{"version":"3.5.0-alpha07","sha1":"c37e432fb8b850a2097a1f7af768bca0063d2a00"},{"version":"3.5.0-alpha08","sha1":"37ed4b0880bf97967f3754ee204c4df10cd9096a"},{"version":"3.5.0-alpha09","sha1":"86a530c440c131e4c162e68b3b3780df1399c4f"},{"version":"3.5.0-alpha10","sha1":"52c7f233c08fc0f2065da4fb2727dc94ba8ff4d4"},{"version":"3.5.0-alpha11","sha1":"35e55a0db57f239de42558de04837324a27c4b"},{"version":"3.5.0-alpha12","sha1":"5f3042de52d3bdba5c600f45a1b27f118de79bec"},{"version":"3.5.0-alpha13","sha1":"ae38c5f66ea685d68b5d719e9a865d653ad58875"},{"version":"3.5.0-beta01","sha1":"d30978749979414c9025c79d07c2f4b2671de1d5"},{"version":"3.5.0-beta02","sha1":"d6502ec6a8a7c199637200614376835fad114b18"},{"version":"3.5.0-beta03","sha1":"f72f929af6334358a673dbbd801e6a925c133835"},{"version":"3.5.0-beta04","sha1":"b293359ece35967efbcd2d624ee364aa7756b7d7"},{"version":"3.5.0-beta05","sha1":"e636adae22da2249acc82d0d3d718f84afe55a5e"},{"version":"3.6.0-alpha01","sha1":"79a4506ba048ceeffd287dfeb595cc7b7ec4cf70"},{"version":"3.6.0-alpha02","sha1":"679431371226ba4ac6527c3bdf49bba30ae7f8a2"},{"version":"3.6.0-alpha03","sha1":"3734caaaa9d8fde9e663e207f8500c7cd995a181"},{"version":"3.6.0-alpha04","sha1":"2d530e0f066f74726760d2323aed98aa31904c4f"}]},{"package":"databinding-common","versions":[{"version":"3.2.0-alpha12","sha1":"1bf2f668ac8a32874534545055cff5ab69882f98"},{"version":"3.2.0-alpha13","sha1":"a621d9617b63f4940e7282828d0be0a2e102b6e4"},{"version":"3.2.0-alpha14","sha1":"67ef3783100c1c37b1bfd82222c7f28c2802da23"},{"version":"3.2.0-alpha15","sha1":"6a86a817d10ab6e1078cf47e7d19c205cd2a5d3f"},{"version":"3.2.0-alpha16","sha1":"260c9a9178ba3c2dfa82902c70a1ca8c668661de"},{"version":"3.2.0-alpha17","sha1":"c3d2e8ed682197bbb686a62d9562a4f3279f7706"},{"version":"3.2.0-alpha18","sha1":"d31f53e45203b0869bbc26facdca7c5756585060"},{"version":"3.2.0-beta01","sha1":"c6737b550c76d8f11d4d1cf4b10029ec28cbec14"},{"version":"3.2.0-beta02","sha1":"fda2d50cb5df85596a7ce38746e9b0c6ccbb0659"},{"version":"3.2.0-beta03","sha1":"3585784486037070b44d8ad82709a4045ec13a5d"},{"version":"3.2.0-beta04","sha1":"f79c8dbc5632e1aa0eb508d176cfdb3cb434c3e4"},{"version":"3.2.0-beta05","sha1":"4c71b165b597f68a53372a5fbc7f2ab731486ad9"},{"version":"3.2.0-rc01","sha1":"ce54a14aed8c43d881fe2493113ce84dfb589f5b"},{"version":"3.2.0-rc02","sha1":"4e1fc5265cb25451f0de672d6abf3c0c666ef3cc"},{"version":"3.2.0-rc03","sha1":"4ae9686c5f7f6e0329a8dfc8d373c5eafcf1c9ba"},{"version":"3.2.0","sha1":"8908b4818c8f5add9fb9e33130135e96be1bb6ab"},{"version":"3.2.1","sha1":"91c10c7ce255ab29ec4aea8a469d6c8ffa60bd53"},{"version":"3.3.0-alpha01","sha1":"acf27762693618dbfc5b841ae7661ef9c2af5521"},{"version":"3.3.0-alpha02","sha1":"815e94080d5631c7fd5e4e325264ab74cdbda13"},{"version":"3.3.0-alpha03","sha1":"a393ca5fb6181be6931032058d389429905d57aa"},{"version":"3.3.0-alpha04","sha1":"86a85e725966f2982e6ddb6ca441e2c47087422d"},{"version":"3.3.0-alpha05","sha1":"666f8ba5b5501378d8bac836976e2f9f243ccb75"},{"version":"3.3.0-alpha06","sha1":"a5fd0bbd45b47be672b0210afdd1af7518ef69c1"},{"version":"3.3.0-alpha07","sha1":"ff112170575230b61d904911a966fae5d2c9388b"},{"version":"3.3.0-alpha08","sha1":"5690b0097d7f9cab98aafba71303e6aa762cd405"},{"version":"3.3.0-alpha09","sha1":"c3dbda4193bbc41ea17c5f2c8f8703ff01b4d300"},{"version":"3.3.0-alpha10","sha1":"48d2464898335a6ef26fe98fcb1c9acf95cf0da8"},{"version":"3.3.0-alpha11","sha1":"607fd0537374aa04b0f5244515d3205a777ed335"},{"version":"3.3.0-alpha12","sha1":"e308342d156886ccb32256e3c4078b58fefc401"},{"version":"3.3.0-alpha13","sha1":"17651c4b9a5a6698bed35db9770602731c61c85"},{"version":"3.3.0-beta01","sha1":"e49a2fd726931909d718fc41f62b3eaf94064366"},{"version":"3.3.0-beta02","sha1":"a83f33d5a43a2fbcd7e921d31a89e60416d723c1"},{"version":"3.3.0-beta03","sha1":"3ca39761e33dbe149f254d1a63d721ca34db0a09"},{"version":"3.3.0-beta04","sha1":"d81b5768fccdbd964fe875799ec5bc0015818169"},{"version":"3.3.0-rc01","sha1":"de969b9675e3ba717574ff2238422e7df8e5389"},{"version":"3.3.0-rc02","sha1":"2115dddc1ca2fe6713c3718c4410a658dffed377"},{"version":"3.3.0-rc03","sha1":"22705d1db1e9ec0a27d7107a802ca645c32a01d7"},{"version":"3.3.0","sha1":"9df1ac00e230e8e9aae9c6fae20415ac31dc5253"},{"version":"3.3.1","sha1":"45795e2f688474b219d37beb7f9f6756578e6454"},{"version":"3.3.2","sha1":"c3d3a2bd4075de662778b79f7bcf480ca1d4e0d9"},{"version":"3.4.0-alpha01","sha1":"2864bdbbbad0556e13299bf47d84aad426576601"},{"version":"3.4.0-alpha02","sha1":"2a4910122e10bc54ead6974cbaa65a7656d40fdb"},{"version":"3.4.0-alpha03","sha1":"d0ed529ae36f05121131220ed70c74756c3ece43"},{"version":"3.4.0-alpha04","sha1":"22647f8dd885eebfbd37c3ad838680d1e237ad82"},{"version":"3.4.0-alpha05","sha1":"a395b3d37ed4aedb263452bdcbdb4223932924e1"},{"version":"3.4.0-alpha06","sha1":"2fa8642b35f4252acdc14626e844bef4827b4b0c"},{"version":"3.4.0-alpha07","sha1":"f61e92689b429f227bce8fed67f37ec51677d257"},{"version":"3.4.0-alpha08","sha1":"6bd7fbcca0e21b8cac5b943afc5f2a1e932ca07c"},{"version":"3.4.0-alpha09","sha1":"d60938886a8ec21e25f25595a7cec07a68a11891"},{"version":"3.4.0-alpha10","sha1":"8e48a0e5824e8fc6ffe9785970c2ff1cb200f5a5"},{"version":"3.4.0-beta01","sha1":"cbfed3d9256ea11a677bad2b21fd58c3f94b0742"},{"version":"3.4.0-beta02","sha1":"836e15692181237f533af939d69dfcc71a258f6d"},{"version":"3.4.0-beta03","sha1":"2944ec7955ad162f7c50b036e8b56bc76eb69f92"},{"version":"3.4.0-beta04","sha1":"5a04b7da7c64c203b51502d485b6cd2a81ea1a8d"},{"version":"3.4.0-beta05","sha1":"ecadcb8660e546624b4bff5391dc75f906ce42e4"},{"version":"3.4.0-rc01","sha1":"87f2de462aa5010237b4e685b5fc9f60926a4772"},{"version":"3.4.0-rc02","sha1":"72edb9c5c6ca1b8ca8ce479ae432e167b2474c6a"},{"version":"3.4.0-rc03","sha1":"ac4a0a9cbf89d5e9ddda9def9e9f3ee1b6e084d9"},{"version":"3.4.0","sha1":"265b8ca5d867c32dd0b9032bd7e7bf49a1af199"},{"version":"3.4.1","sha1":"f43d4dc7b8af8e7f9a72e972f104572f9ecd3630"},{"version":"3.4.2","sha1":"b7c75633f0e744572f7e4264487a35eaf9de11ad"},{"version":"3.5.0-alpha01","sha1":"9a41ded24d1028866c44c5eb409ff0c7d5da8adc"},{"version":"3.5.0-alpha02","sha1":"c4e0a1fa6d106d41ddbd529690acf27b6e0bde78"},{"version":"3.5.0-alpha03","sha1":"355bc2c290319de540dcb780c93a93c4681d6e0e"},{"version":"3.5.0-alpha04","sha1":"3074680053f7ed63c0ad55aac470db235f8b060d"},{"version":"3.5.0-alpha05","sha1":"c79d9781e523768260ea873262b14d0c07ee2c5b"},{"version":"3.5.0-alpha06","sha1":"9f74a4c002ee6c71b28ea0992254365b52353591"},{"version":"3.5.0-alpha07","sha1":"fcb7b4cf7db2746c95bc030cb6742de4bedef410"},{"version":"3.5.0-alpha08","sha1":"4be0a2694e9db77be7b76836fc467bc234e39f09"},{"version":"3.5.0-alpha09","sha1":"4fd4190f834b6f01372789443daf4011f7b3b58b"},{"version":"3.5.0-alpha10","sha1":"22c39c19e36f907e9fbb3f7a9f00d2523ee04d07"},{"version":"3.5.0-alpha11","sha1":"eaced5cbefa8a495ed94a6b3ec9ec440698249e3"},{"version":"3.5.0-alpha12","sha1":"119450c3161e8bd59ccfe890855c30fc7afc13d0"},{"version":"3.5.0-alpha13","sha1":"50d7153d0df8f69774079a8841887edb6f35fc46"},{"version":"3.5.0-beta01","sha1":"f4f1351ff1775c87ed18c248e3c446f7ebb337e1"},{"version":"3.5.0-beta02","sha1":"fae864ec9bc7576b87b9df737b675eebc2f53f1"},{"version":"3.5.0-beta03","sha1":"a0b24f93f81761139573e073fa69f03c3a6bb84f"},{"version":"3.5.0-beta04","sha1":"c9d280ebb9506821039338166b50651c6656961"},{"version":"3.5.0-beta05","sha1":"274896b674317289af7405a7948c75e8fc1f9558"},{"version":"3.6.0-alpha01","sha1":"e86aadab9812a72da47851590f195fb8f1a51294"},{"version":"3.6.0-alpha02","sha1":"4452b041579fb9df4869ba3bcc63bbf35723272f"},{"version":"3.6.0-alpha03","sha1":"5249b36dcc9386eecd2a36c45633203d00b77d"},{"version":"3.6.0-alpha04","sha1":"e2f617cbafde483287638a22e782215f5b835039"}]},{"package":"viewbinding","versions":[{"version":"3.6.0-alpha01","sha1":"84bdac8c2aed5013822592eb76b5f8fb806c6559"},{"version":"3.6.0-alpha02","sha1":"9d0c3691b8453f9c8117a2c7ce251e83790f1049"},{"version":"3.6.0-alpha03","sha1":"383165c4f98edd20983ea2791bb21dd82bc7636"},{"version":"3.6.0-alpha04","sha1":"a89f7105e010ad575d2ebc968561ed8d1d751f99"}]}]},{"group":"androidx.constraintlayout","update_time":-1,"packages":[{"package":"constraintlayout-solver","versions":[{"version":"1.1.0","sha1":"90837bbae018cb1de85527c798c8762063ab8ffa"},{"version":"1.1.1","sha1":"404e34e56f0dc5b4682adafaa49a41d68720479e"},{"version":"1.1.2","sha1":"d324137427d23f643b61a2ad1cb20df2b8b1eb97"},{"version":"1.1.3","sha1":"54abe9ffb22cc9019b0b6fcc10f185cc4e67b34e"},{"version":"2.0.0-alpha1","sha1":"4007460f0df3e1d3fde9436bb67ae2045a08bd"},{"version":"2.0.0-alpha2","sha1":"3531f0f78e59180b9b9017c98e73262d2eac1a68"},{"version":"2.0.0-alpha3","sha1":"2a8ac731e821a10465b111f9e9f52e3e0b0d45b2"},{"version":"2.0.0-alpha4","sha1":"51db6ff9ce89f2e0a51f51a64635c3fc4330d1db"},{"version":"2.0.0-alpha5","sha1":"5648200305b67720417e6c97d8436f1f367ae93a"},{"version":"2.0.0-beta1","sha1":"bb633865f9c53460d99ee06b66e61a31440f2c69"},{"version":"2.0.0-beta2","sha1":"eeefeaa292ad9cd529d6025fe3758eddd946b2f0"}]},{"package":"constraintlayout","versions":[{"version":"1.1.0","sha1":"e3a6f67e88f5d31eee02e2e26d2fe42dcbeb5cbc"},{"version":"1.1.1","sha1":"f338f406351eed62564db17e848ce44ded1b8bf7"},{"version":"1.1.2","sha1":"8890843da2da71c82e4503ad0899397991777cdc"},{"version":"1.1.3","sha1":"2d92d69c428d9d36fd09f5669ec4e60a1e8859a"},{"version":"2.0.0-alpha1","sha1":"799837e91b523965424e880571571436e0ab8527"},{"version":"2.0.0-alpha2","sha1":"c750d8978dc8ad6d216f64bb77f80df722e44a7d"},{"version":"2.0.0-alpha3","sha1":"63ed4eb8784f5933561ee1e534a78f884f079c2e"},{"version":"2.0.0-alpha4","sha1":"e538867c1b1d5c71cd84bd042b90047f275a28ab"},{"version":"2.0.0-alpha5","sha1":"8672bc51692b58dafe42670c04cf0f7d04b6e13d"},{"version":"2.0.0-beta1","sha1":"5d4177e74ac54d7d6ee5aa6131f7f3fcee9b8fed"},{"version":"2.0.0-beta2","sha1":"159145cde2341b20d646c97a781d554000752198"}]}]},{"group":"org.chromium.net","update_time":-1,"packages":[{"package":"cronet-fallback","versions":[{"version":"66.3350.12-alpha","sha1":"736acc480722494e669d2f3ef7b553f71c318d36"},{"version":"66.3352.3-alpha","sha1":"627de315169d9ae4e56a0fd7485113041f38235e"},{"version":"66.3359.158","sha1":"5a91907e8589076319cbcb2a69967380fe7c3615"},{"version":"67.3396.81-alpha","sha1":"86636fd220722cace87d8f22f9c9f1fb52c006e3"},{"version":"69.3497.100","sha1":"5765f52f47bb19321982d9baf859d1358a7cd0b7"},{"version":"70.3538.110","sha1":"89e55ac39d1ac6f89c646122b6b9ac181de6446f"},{"version":"71.3578.98","sha1":"153826c568048452889b5f101009908d631c42f5"},{"version":"72.3626.96","sha1":"c023e092aec6aefaf096f754a9150172f49aafe7"},{"version":"73.3683.76","sha1":"199297e10b5dec1627237f41c0f611016e507fac"},{"version":"74.3729.136","sha1":"a05253d0504a7e6a0dc758a5f00bf25545d290be"},{"version":"75.3770.101","sha1":"7ec16f862f04f0f3e07a334bb548931b5f90700c"}]},{"package":"cronet-common","versions":[{"version":"66.3350.12-alpha","sha1":"c9a4cf88efe3a0405b2b7ccdbf961d07ee96bac1"},{"version":"66.3352.3-alpha","sha1":"67dcd4c086a0f1f6b0d21900ced665efd0e75677"},{"version":"66.3359.158","sha1":"bc668287591849350eeb3ae120230ad51afc5c8c"},{"version":"67.3396.81-alpha","sha1":"b3c9f07e1a156ad763c8d5c0c0d9aa094fdfb264"},{"version":"69.3497.100","sha1":"63080c7061fc7a8acbe76e42160584521b901191"},{"version":"70.3538.110","sha1":"85ef048783121f5c9eea7811a3b0c86ae5ca5fc3"},{"version":"71.3578.98","sha1":"1adf4d5f727d6ba4534a4debfdd640d1d36ab14c"},{"version":"72.3626.96","sha1":"e4cda2977e38c3e18b009bd222c62ae0a8ac63c2"},{"version":"73.3683.76","sha1":"f0ba84cdc533556c89ab54312aaf60fd7b330ef0"},{"version":"74.3729.136","sha1":"3b012fc9c1604bdca9a88bc22ff87020b2c88f6a"},{"version":"75.3770.101","sha1":"6ae9c608be1a012b3e3fbecfa040d29ac2feef0"}]},{"package":"cronet-embedded","versions":[{"version":"66.3350.12-alpha","sha1":"c278c2a21a677155b246fba26a52ce066d9a0b9"},{"version":"66.3352.3-alpha","sha1":"fbb53c8a29780128a0cfbbaeb8cc73f66d767422"},{"version":"66.3359.158","sha1":"90f053940769442f1fb7cdb114cff5b23b2fdec3"},{"version":"67.3396.81-alpha","sha1":"2c3c5e0a6375cd99583c6a97a3d99c14fe2d7afc"},{"version":"69.3497.100","sha1":"d5640acce86d8cd147a0a833324aaf4aba4526ec"},{"version":"70.3538.110","sha1":"42b1f9f097e7b1d545b30a3b22e0a9b28726caf1"},{"version":"71.3578.98","sha1":"cb13aa2e50211e5ddc0b8d67abd01c279084e996"},{"version":"72.3626.96","sha1":"4613fffa98812932968238163f83a2e8c547399"},{"version":"73.3683.76","sha1":"866d0238b06a5e50131798dd8f527b8a8b685a85"},{"version":"74.3729.136","sha1":"584db6c11d37ff7b192da75ba0802ab029bcd"},{"version":"75.3770.101","sha1":"5881bb48728c48a15cbc13aa0491ca7ed56ce5cb"}]},{"package":"cronet-api","versions":[{"version":"66.3350.12-alpha","sha1":"644ae8b2220bc85d474b8136cd4bf699cfa8925f"},{"version":"66.3352.3-alpha","sha1":"733e2caf30b1aba52c10976405672d84e41d06d9"},{"version":"66.3359.158","sha1":"df8412bd8597234ac50de697b62bb10f27f44995"},{"version":"67.3396.81-alpha","sha1":"e86e2df8aef2b86f47a8e15a1a89272e68dc8393"},{"version":"69.3497.100","sha1":"1f77c6c7628fb6717e49160ecd1d0916eb03e391"},{"version":"70.3538.110","sha1":"fe3035fd1eedcf2bc4c6611db2534af3fb54e3f2"},{"version":"71.3578.98","sha1":"406787e8544a64c32922f8067280be1666f1fcd"},{"version":"72.3626.96","sha1":"46703db069eb4da167053f185beaa81b4a9e2c47"},{"version":"73.3683.76","sha1":"fee8e696e9cd568e2d8503e16b76eceeab573857"},{"version":"74.3729.136","sha1":"b2fa0316baa142d72c35f28cf15fdab54d5c7d2d"},{"version":"75.3770.101","sha1":"afbcb1fd59c615ebbc145b4c7ea21f5a8e451bc0"}]}]},{"group":"com.google.android.play","update_time":-1,"packages":[{"package":"core","versions":[{"version":"1.2.0","sha1":"803e450bb9aa513817bf85dac2343b8a9803a7a3"},{"version":"1.3.0","sha1":"ab991500718346e29e5933895acaca7731215b03"},{"version":"1.3.1","sha1":"97c61545c10253ee20125d71f687cec50a61ff34"},{"version":"1.3.2","sha1":"5affc8f3419cce9e3d8d4a21529d769024a607cd"},{"version":"1.3.3","sha1":"bbca4669b8cddb42142151cf4c0d5fae718b73e9"},{"version":"1.3.4","sha1":"a28250c37ebe64be41f76604e6e93d7e5704d9dd"},{"version":"1.3.5","sha1":"da780569034c3d2ccf82dd72e54bdc37acd8ef54"},{"version":"1.3.6","sha1":"2cc6ee279d1f2220a1626b62d85e2df3189b96cf"},{"version":"1.3.7","sha1":"1560eed326fed7a011219f7f191ac92afab39f1c"},{"version":"1.4.0","sha1":"d44f580a9f19290e2ec32e72df1f442a548c26fa"},{"version":"1.4.1","sha1":"da0c3935a07e08f0a12af8d89e21102732c8efc0"},{"version":"1.5.0","sha1":"19039bbc4213ec649e51267896cb7773b50b3e7"},{"version":"1.6.0","sha1":"24d0fba2d64039a5b5d40a453196d79c456cd76a"},{"version":"1.6.1","sha1":"f604516fdf8e53d45f8ce553544c1a5967c8c771"}]}]},{"group":"androidx.multidex","update_time":-1,"packages":[{"package":"multidex","versions":[{"version":"2.0.0","sha1":"a08159a4d031fe376a0b5da26ee90f26d8c4d12b"},{"version":"2.0.1","sha1":"c5c10c06f82ec4b4fcccc5ee4ea64ecc48254f33"}]},{"package":"multidex-instrumentation","versions":[{"version":"2.0.0","sha1":"1444e4588225bdef857adac8ce35ea5843f0e46e"}]}]},{"group":"com.google.android.material","update_time":-1,"packages":[{"package":"material","versions":[{"version":"1.0.0-alpha1","sha1":"525adf5dad2af091dca6f7285ed60987af1edf75"},{"version":"1.0.0-alpha3","sha1":"43f64529b30ec9223d945efc192c0c3639432df9"},{"version":"1.0.0-beta01","sha1":"3632bf5cfc013b507a4c579d16de3efa3e142d83"},{"version":"1.0.0-rc01","sha1":"f83e012c22d2fac8fcf23880d7167832b099fa94"},{"version":"1.0.0-rc02","sha1":"f83e012c22d2fac8fcf23880d7167832b099fa94"},{"version":"1.0.0","sha1":"f83e012c22d2fac8fcf23880d7167832b099fa94"},{"version":"1.1.0-alpha01","sha1":"3ae32bfcdb148bb23a2914fc67d48fb1aec4730d"},{"version":"1.1.0-alpha02","sha1":"70433f8012f0859c93571791c620e76f1ccf2eaa"},{"version":"1.1.0-alpha03","sha1":"e40a472046d8ade67cd42adf2d6b40541abf2d7d"},{"version":"1.1.0-alpha04","sha1":"a7dcec45437b3786e64f44c3a8441f5f9e21305f"},{"version":"1.1.0-alpha05","sha1":"3aeef2df7c7b18972f48a92b01ccd865771f28c"},{"version":"1.1.0-alpha06","sha1":"c3824890c4bdb3f9f464819e00f6a29ce75dd28"},{"version":"1.1.0-alpha07","sha1":"af8d8778f498e21bd0d8bd3d002f48ee77984e5c"},{"version":"1.1.0-alpha08","sha1":"24ddfce9db5e1d0d043ffd52dee522494584d870"}]}]},{"group":"androidx.test.services","update_time":-1,"packages":[{"package":"test-services","versions":[{"version":"1.1.0-alpha1","sha1":"52451e8f752869f0430d150f0dd99f7473ac490d"},{"version":"1.1.0-alpha2","sha1":"3652f8e09954694a77aca900ffddb1705d867ce5"},{"version":"1.1.0-alpha3","sha1":"53ffc4179934adc96347158d74ce7015df8cde0"},{"version":"1.1.0-alpha4","sha1":"8b2af9dc032bd8dc4777adce2a917a0cb295220e"},{"version":"1.1.0-beta01","sha1":"ab6ac2f5cd72fe57c763409ecc029634e0957958"},{"version":"1.1.0-beta02","sha1":"d8fb5e5f6973f869c6ba490cd505d555a3ba9c56"},{"version":"1.1.0","sha1":"13fa08e5a748bd4ed7a99894e33a0b95f69e5a0"},{"version":"1.1.1-alpha01","sha1":"908ef73b062e63f6f133e8120240dd8eaf8576a8"},{"version":"1.1.1-beta01","sha1":"3362267e228ef0d26f356b09092220cf739daa89"},{"version":"1.1.1","sha1":"9dd8143d514bccd46ba59db1011d8a8eedf47a3b"},{"version":"1.1.2-alpha01","sha1":"5fd80df21b6c8e9777371ca8db38e39843179370"},{"version":"1.1.2-alpha02","sha1":"b4fb88a3d83d8702385fc6799503f7963e3635db"},{"version":"1.2.0-alpha03","sha1":"1ad3a450d0013178a52077f6abb150b2e0ec792"},{"version":"1.2.0-alpha04","sha1":"97aa62cd11d6cd97159f3236ea34557dd2834e7b"},{"version":"1.2.0-alpha05","sha1":"28c58406817799de9157480b8a86d63c407146a6"},{"version":"1.2.0-beta01","sha1":"561799e35eb705127ceba94a29cc0261c1b07683"},{"version":"1.2.0","sha1":"58c9ac48a725a1a0437f4f6be352a42f60ed5a7d"},{"version":"1.3.0-alpha01","sha1":"6beb681034b7167d7a4f90a0349a72530d905c07"}]}]},{"group":"androidx.test.janktesthelper","update_time":-1,"packages":[{"package":"janktesthelper-v23","versions":[{"version":"1.0.1-alpha1","sha1":"2166ef25ec4ba59eba65c55d02c12eefe2d53620"}]},{"package":"janktesthelper","versions":[{"version":"1.0.1-alpha3","sha1":"6863067846b43429d3f020aed410b43dc3b52e54"},{"version":"1.0.1-alpha4","sha1":"e1b0727bb047f89c436e3e8a73fddba19a188c64"},{"version":"1.0.1-beta01","sha1":"1f45d10f7cf636d48290480d1986d373b6bef07e"},{"version":"1.0.1-beta02","sha1":"4638b2302750588156b4c919145e3f7d48525d8b"},{"version":"1.0.1","sha1":"5cbd9c606e5a0c1e69e550082d6d8bdeec9e3336"}]}]},{"group":"androidx.test","update_time":-1,"packages":[{"package":"monitor","versions":[{"version":"1.1.0-alpha1","sha1":"84f414f3e11ea38de0371b9dcef81f80ef7edf1d"},{"version":"1.1.0-alpha2","sha1":"5614841f3cdbbc72e6ed9877bc0c5c4f50fdead0"},{"version":"1.1.0-alpha3","sha1":"fc296ede2d45a9d894fbafc6bb2219f1f2efe4a5"},{"version":"1.1.0-alpha4","sha1":"bb43b243e1bf79f578f864c56fb7e869cabda197"},{"version":"1.1.0-beta01","sha1":"edc47a76ea27d708101bec47591975ad5d449a83"},{"version":"1.1.0-beta02","sha1":"85111f4d5a6b40a491954270b21cf1ddb42e18"},{"version":"1.1.0","sha1":"172e69620068ebc70a9e82542ec70292437c2c97"},{"version":"1.1.1-alpha01","sha1":"7ccee15b42244e2e0e899b2b8ff764ef848e3348"},{"version":"1.1.1-beta01","sha1":"f999c48e8b4ed1c546d292a4c5b6a87f5a9c5792"},{"version":"1.1.1","sha1":"443c2f33d4e19f868cd4e4437909ec8dcf43f053"},{"version":"1.1.2-alpha01","sha1":"bfc13f37dc33a6733fd9f8181824c5687b530d17"},{"version":"1.1.2-alpha02","sha1":"68fea6b90c3e89d32ecd02fea9541005a69ee499"},{"version":"1.2.0-alpha03","sha1":"789eeb98f8c6d9915d7d568e81de97b81e59a9c7"},{"version":"1.2.0-alpha04","sha1":"a6a1b6a14599a113f70f871e6183ee9112882f7d"},{"version":"1.2.0-alpha05","sha1":"67a047f803560dba7dad79946a726a1e384e21ec"},{"version":"1.2.0-beta01","sha1":"5de6b92cc522e5b8e71cfd61b611974b3fea9a64"},{"version":"1.2.0","sha1":"d2f75d117c055f35c8ebbd4f96fabc2137df9e4d"},{"version":"1.3.0-alpha01","sha1":"f9d4d2db4991a767f7a71c4b6cbe2ea3288e9227"}]},{"package":"runner","versions":[{"version":"1.1.0-alpha1","sha1":"c1d4964bf93b39c006b665559ae3d0c96816ff1"},{"version":"1.1.0-alpha2","sha1":"668a463f9754dd58b3d736b5b7d879f0ec5eb9dc"},{"version":"1.1.0-alpha3","sha1":"327aba100352160900c06f43f2d6d266c2a2f8af"},{"version":"1.1.0-alpha4","sha1":"325131d4c2ae5d011c485dfba284a0bccca606db"},{"version":"1.1.0-beta01","sha1":"a137e2097797612c82947cfb4fd25cebc8097727"},{"version":"1.1.0-beta02","sha1":"1ca867200be8e290a8e11be8b4f194eb442b9597"},{"version":"1.1.0","sha1":"20989c24d38da672224fa7742c038d552b1744a8"},{"version":"1.1.1-alpha01","sha1":"bbc6fa37e1fdba71d92fd4674f806f4491c663e2"},{"version":"1.1.1-beta01","sha1":"e1f8896061374e173a5b93f9ddcda9857783e75b"},{"version":"1.1.1","sha1":"810a7aacb5106d92cdf648b2497694c4ebf73500"},{"version":"1.1.2-alpha01","sha1":"1bbc49f186013d398a852c3607914d74be450559"},{"version":"1.1.2-alpha02","sha1":"285cb9a827d8fdf0eaa9bf4a0a722c5784d5f7b2"},{"version":"1.2.0-alpha03","sha1":"9aa3c2e76a29b785b569ab66888e617e49790b0e"},{"version":"1.2.0-alpha04","sha1":"c019aae0aabed0d7be1b4a9c160930edc04eb3d9"},{"version":"1.2.0-alpha05","sha1":"923769d127e24307f3fcd81eb0111ab2f919c604"},{"version":"1.2.0-beta01","sha1":"2e9a54b58c1e61659739ef47bb294459b3d63c63"},{"version":"1.2.0","sha1":"237b061c45b3b450f88dd8cde87375ab22b0496a"},{"version":"1.3.0-alpha01","sha1":"ac1310d9bf592c4830029c3c50aa770b56c53270"}]},{"package":"orchestrator","versions":[{"version":"1.1.0-alpha1","sha1":"742642b3b63518bc7f2af5a7955fad01a1d1d2e8"},{"version":"1.1.0-alpha2","sha1":"e4a93ebe3976861420c479ba0bba47f994cee7d0"},{"version":"1.1.0-alpha3","sha1":"2131e94d82bcc0eb6197f1fce8753cae37b01619"},{"version":"1.1.0-alpha4","sha1":"6252c4d29c7925b1249d8733713300f1e6fead8a"},{"version":"1.1.0-beta01","sha1":"926734a07c2956ac45597600ac6f3757a0a0742e"},{"version":"1.1.0-beta02","sha1":"cd36ee8a143997abab66f3c4512cee164bcdaf02"},{"version":"1.1.0","sha1":"fcc19d304b031949a425e0f544cd89ecf216656"},{"version":"1.1.1-alpha01","sha1":"bb943a3e118a4c9b64201924980713bd70ad0a48"},{"version":"1.1.1-beta01","sha1":"aa19f907b4b8324aa6f2df3eccb9068e8cb5d1f4"},{"version":"1.1.1","sha1":"7553b6b0848f4ac80a15520d82df2e261f156091"},{"version":"1.1.2-alpha01","sha1":"f267f1d26d4fa574a36fd9e50527a0eb39314eeb"},{"version":"1.1.2-alpha02","sha1":"242a56e9698a753004823d2a8a6e8eb798abf615"},{"version":"1.2.0-alpha03","sha1":"596ad2f03ee54c84c8a0296b7f837b49956cee0a"},{"version":"1.2.0-alpha04","sha1":"44e92573535f8a96232024e80f469045fa94b3fe"},{"version":"1.2.0-alpha05","sha1":"e2b783619ac6b044031df5cc3ac6a461fc5abc6c"},{"version":"1.2.0-beta01","sha1":"959d60e25afe067353e811d693a0f75ca7a1254"},{"version":"1.2.0","sha1":"e3edf4e08d2d3db127c1b56bbe405b90b64daa5"},{"version":"1.3.0-alpha01","sha1":"fad039d65aff6a39f3bcdd622dfa07584ef20d0"}]},{"package":"rules","versions":[{"version":"1.1.0-alpha1","sha1":"c448a4c3d98fe87c95cbeb14cfdad8a96b8060f1"},{"version":"1.1.0-alpha2","sha1":"368a491ddb6bd4d3deaa360e5d3d0207b6b3ff36"},{"version":"1.1.0-alpha3","sha1":"2d346978e76df4f11069ee4c4670a4cacec5f209"},{"version":"1.1.0-alpha4","sha1":"8c03387588168af92d3b58f4e8c86f5e314baaed"},{"version":"1.1.0-beta01","sha1":"3cd4856f41b81c332a03ef6ee0936628535b1fa6"},{"version":"1.1.0-beta02","sha1":"aaa0242ff4b3529a91dea97309e1c422b9274e32"},{"version":"1.1.0","sha1":"b0ba089240f7507f0793021525e0b02775e3f768"},{"version":"1.1.1-alpha01","sha1":"5904af94f3a0f2dde8907b5f87280bc28208c926"},{"version":"1.1.1-beta01","sha1":"cb2ab7a4b12634e9beb1060b1299b64ea82dbdf7"},{"version":"1.1.1","sha1":"1bc1f460a52e13f6622b7b8691244dcbbf827c10"},{"version":"1.1.2-alpha01","sha1":"2a1e20e591a3c2553988f499a580236a27e11bf"},{"version":"1.1.2-alpha02","sha1":"15d60875167225563600b0fe35f3a6505359c7cc"},{"version":"1.2.0-alpha03","sha1":"a116de1393cf3ff203b563ce4cc7a0d9f18615a6"},{"version":"1.2.0-alpha04","sha1":"7c2f35b7b768992e85358b3b20c01cd6d548966b"},{"version":"1.2.0-alpha05","sha1":"404ab39a155d8210eaacfdc41431bcbf8c10e81e"},{"version":"1.2.0-beta01","sha1":"43c3139db7ac3a496c9024a7eff725b8ed1d7b59"},{"version":"1.2.0","sha1":"91904046e724ac82d9b7fe698e5fe92b871c726e"},{"version":"1.3.0-alpha01","sha1":"104afd0b3b3d43af2a60eb0439b8527b2186d758"}]},{"package":"core","versions":[{"version":"1.0.0-alpha2","sha1":"930b5368a03f1208257708a67dd414a575af272c"},{"version":"1.0.0-alpha3","sha1":"b7440ee144bdfe62e99b16f67f2aaaf62d1e6ff"},{"version":"1.0.0-alpha4","sha1":"49500234f3aab14848b598e5347f7262277e68b7"},{"version":"1.0.0-beta01","sha1":"6521823bbcbc0e22de2a3846fc8c09f7f5f5bfc"},{"version":"1.0.0-beta02","sha1":"269da0a5bde3c30f8ef2acdf50f7326826da231a"},{"version":"1.0.0","sha1":"5b4d8d634b56d334283e76db48478f8280571832"},{"version":"1.1.0-alpha01","sha1":"be7f9857d21a4d3d3acd9cc517deb60d2ad316a6"},{"version":"1.1.0-beta01","sha1":"cab73e6de469e8f6f122b5ff5c739fd4e775b6be"},{"version":"1.1.0","sha1":"6e5297f48536307e5bacecd6c3e6febb54671828"},{"version":"1.1.1-alpha01","sha1":"1052cdf9a1621046305fc5ea913e5dd5244781f6"},{"version":"1.1.1-alpha02","sha1":"299ae3d08168007a2e12185164f50cb6db07c1e4"},{"version":"1.2.0-alpha03","sha1":"3b2a927e57375b8dd0bb8afea6ec5338cc49b297"},{"version":"1.2.0-alpha04","sha1":"da9aa614d6eb15d0cb5e723316c653b42790a150"},{"version":"1.2.0-alpha05","sha1":"4dfa276bbd7b98bed8e8e0fbabca8cd3e1c7471f"},{"version":"1.2.0-beta01","sha1":"5450a52d419eadd5d0d37fa092225f1390fdd7fe"},{"version":"1.2.0","sha1":"81ff9280a5a04cd523fbfb0b84b69e792dc78453"},{"version":"1.2.1-alpha01","sha1":"7d5c432ac319d6950106cb536312c7d5b286c490"}]},{"package":"core-ktx","versions":[{"version":"1.1.0-alpha01","sha1":"5750cbc8b0379837df8ba54db616d8605b5e2251"},{"version":"1.1.0-beta01","sha1":"6a3101dd9a963c810290c08b6bea36c11dcf5a49"},{"version":"1.1.0","sha1":"f8b8536719624833ec35c96f1bd0d848095bc39b"},{"version":"1.1.1-alpha01","sha1":"cb546ecc0df30ffea29c1f5b9358fba0b2e88863"},{"version":"1.1.1-alpha02","sha1":"96579e1fd73b94a91303313bf2a60d1e6de458b8"},{"version":"1.2.0-alpha03","sha1":"face5462e4a7bddc42727dc62ac381cb30df03f"},{"version":"1.2.0-alpha04","sha1":"343f9931214c81c09ff85cacfe12c5ec85702e7e"},{"version":"1.2.0-alpha05","sha1":"561887a77df4d8d24772643326ffb099e408ad33"},{"version":"1.2.0-beta01","sha1":"5c68871ae48679e17c70bffcb14b28c54238b188"},{"version":"1.2.0","sha1":"380d8ee77386fd22fed2090915542be642a93305"},{"version":"1.2.1-alpha01","sha1":"150b7059463350789921bd8db2658ad2fad15514"}]}]},{"group":"androidx.test.espresso","update_time":-1,"packages":[{"package":"espresso-remote","versions":[{"version":"3.1.0-alpha1","sha1":"760d7f7c6c8b34a3d3ef9a69c779f439b8834e97"},{"version":"3.1.0-alpha2","sha1":"d16acf017e6b031ac2942dd80f52beae3a7e1283"},{"version":"3.1.0-alpha3","sha1":"ecf415cca3d6334bd673acbe671f47e0ec6c5f70"},{"version":"3.1.0-alpha4","sha1":"1f3dd9ca998c89ed7d84f1bb9f7e34637d6da8b7"},{"version":"3.1.0-beta01","sha1":"aab5276697c544725794b0cb149947af2b492728"},{"version":"3.1.0-beta02","sha1":"d6a02c723f9a61f6f8302028a298fa0e690479d6"},{"version":"3.1.0","sha1":"5542c7c28699acbda8c2aef8bdf22f9ce9ac517e"},{"version":"3.1.1-alpha01","sha1":"50f633100ca1ea9170f998caaa9eb5b75ba132d9"},{"version":"3.1.1-beta01","sha1":"86c4e2afe5dbda1fada1cda4e69d2b14876539c0"},{"version":"3.1.1","sha1":"11c1b972ad574f1f54aba9cb5cd4c369bf3cd681"},{"version":"3.1.2-alpha01","sha1":"e855801e3dad39a785c40e33014ff103b673211d"},{"version":"3.2.0-alpha02","sha1":"2ba5ef3cb704ad1b5340bcb976417c7cc13093f1"},{"version":"3.2.0-alpha03","sha1":"c3c8b20bd22d084b6e9e8f73974d5dc7ed6607fc"},{"version":"3.2.0-alpha04","sha1":"a9d1b4a0946a0c6f216267f5b6e68f4537506298"},{"version":"3.2.0-alpha05","sha1":"bf6159e23778abf9d7baf25cc91ed867bc902c39"},{"version":"3.2.0-beta01","sha1":"f160fd9a823a1973b5acb1c93556ee18ccd838ce"},{"version":"3.2.0","sha1":"ddefc5c16cf70cbcf4e43726da71363accacb476"},{"version":"3.3.0-alpha01","sha1":"c0ff1cd8a27494261da95c0dd6a57f7269ec3a93"}]},{"package":"espresso-contrib","versions":[{"version":"3.1.0-alpha1","sha1":"fe3ab888522edba183ecd2fcc0d843c2cedf7b75"},{"version":"3.1.0-alpha2","sha1":"b5ebcc7ae7ca8f3353a1565695940906bf07363a"},{"version":"3.1.0-alpha3","sha1":"ba79fd40b1e493462126492b3b775b0394b8c620"},{"version":"3.1.0-alpha4","sha1":"7f9cd36f29301d2568fdbaaf0f9353aa28d825aa"},{"version":"3.1.0-beta01","sha1":"e3a14ccd03f9a0b43210177da188a04b50fce9f3"},{"version":"3.1.0-beta02","sha1":"3f09214b1ac757b070cb8c932c2deba69075c342"},{"version":"3.1.0","sha1":"ea82bbe92d4a448862602b85c8aba713b5bac6d"},{"version":"3.1.1-alpha01","sha1":"17f8954e26d97e809227706ffeea85e0108398da"},{"version":"3.1.1-beta01","sha1":"36b58a067c9a796d2d91e94ad46e9e8b8f3dc4f7"},{"version":"3.1.1","sha1":"33a00ee0f1b6158a1b8b132ebc4e1cb00ffeb23c"},{"version":"3.1.2-alpha01","sha1":"e55120dde9a5dd12222e9659ce833be09bd9c9e6"},{"version":"3.2.0-alpha02","sha1":"f17bb76b4b5947029473566b382423e68ca920f2"},{"version":"3.2.0-alpha03","sha1":"ccc2e6efdf6a5f9723306ea5dd952552a6df7bfc"},{"version":"3.2.0-alpha04","sha1":"3f443e719d3886e01d3a9cf56c107a59069bdb98"},{"version":"3.2.0-alpha05","sha1":"e509a7dde6851adcbda00351e208ed941ea84a4a"},{"version":"3.2.0-beta01","sha1":"a69add2cdd873b2c7ed56351adb480975c0ad690"},{"version":"3.2.0","sha1":"55de2d3fd0f8286c18c3a30c445033f20cc314be"},{"version":"3.3.0-alpha01","sha1":"bf17e82bf3937f336c94cad8889ea180c2b85bad"}]},{"package":"espresso-core","versions":[{"version":"3.1.0-alpha1","sha1":"6ab572e3978600e2a205d09e71952cbe47a0ddc"},{"version":"3.1.0-alpha2","sha1":"9a79181bbe895bd40847089aa889496786e3467b"},{"version":"3.1.0-alpha3","sha1":"36f1084a907d4c5e8602fd7522445912fea3bc4"},{"version":"3.1.0-alpha4","sha1":"d28c854dff83940dff291c102e2f4649d356b546"},{"version":"3.1.0-beta01","sha1":"a13a4d3064787d32aef512ff7515265d82a6d660"},{"version":"3.1.0-beta02","sha1":"dda06d4d60508cf51b820ce21f4de511ea589a17"},{"version":"3.1.0","sha1":"450220d4b83852bdb10413c768a7ad536c63d71d"},{"version":"3.1.1-alpha01","sha1":"4109e8bf3a6406dd3a955cab0ba0e9e28132a2d5"},{"version":"3.1.1-beta01","sha1":"62eb0a6cdc0acafa524f9b75c08465d0c83b3715"},{"version":"3.1.1","sha1":"fbc74f331a022c09be5775bbb0573b6ef3f38ae9"},{"version":"3.1.2-alpha01","sha1":"e18102c258987a1c66860cc5f8def4afff104dec"},{"version":"3.2.0-alpha02","sha1":"68da1648985bfa9489f62c90ae43bb6d25adb7ee"},{"version":"3.2.0-alpha03","sha1":"6857286e07a8df17c99737df6b40e3da7a3fc7c1"},{"version":"3.2.0-alpha04","sha1":"58d1c5dbc3d38d87632133042fb8fb87c1d44a89"},{"version":"3.2.0-alpha05","sha1":"9421948b8bc7bd90c1a06ee2c62137377e834d46"},{"version":"3.2.0-beta01","sha1":"f01e948b9327dc0dbfed33cae9bf18bba594d0e"},{"version":"3.2.0","sha1":"36efe1140d88cc9b958117049a643bacb128b4a9"},{"version":"3.3.0-alpha01","sha1":"9bb4304287cc707e01cc8764f27203f47881ee5a"}]},{"package":"espresso-accessibility","versions":[{"version":"3.1.0-alpha1","sha1":"7cac289662ad47e4810ecf65c6dd3addf4bb605e"},{"version":"3.1.0-alpha2","sha1":"ad989bca1515c83a21c09c71bb43c9ddb186a938"},{"version":"3.1.0-alpha3","sha1":"4e2b2efaeeb169fe42c60f847dc354f424537925"},{"version":"3.1.0-alpha4","sha1":"709a627d9e4beab97e9a9ad7967f6ee2a548a1a2"},{"version":"3.1.0-beta01","sha1":"c18af3ddb8531fd14a70b3cdbfeacd5b7e191362"},{"version":"3.1.0-beta02","sha1":"8c92fcf5d0c6f2db5ddf8bbbc1c36d222a08daa6"},{"version":"3.1.0","sha1":"eb65334649a0f3aaaaafcd41fc11eeddc1bc27d"},{"version":"3.1.1-alpha01","sha1":"c0fab781b6714e14cd8e64093847d9e693c3b1e7"},{"version":"3.1.1-beta01","sha1":"1e115b1273a490e43e56e48ba1b9d97a26a6324d"},{"version":"3.1.1","sha1":"eaf22134ef7de7e9efa0f05973d21960d0567fb2"},{"version":"3.1.2-alpha01","sha1":"c6a309a79d4d57f7f73db55fe52b2c1f353efd6f"},{"version":"3.2.0-alpha02","sha1":"ad54d57157d9649db117ee8ee1bd3b7a286ab124"},{"version":"3.2.0-alpha03","sha1":"35786c509345ab6f86872ee752b89a641e701c60"},{"version":"3.2.0-alpha04","sha1":"69911e1285275a77a8be104d436f2709dac3ca9b"},{"version":"3.2.0-alpha05","sha1":"5384c5a1c30b347bcea6dd027e21b5f3ba64a47e"},{"version":"3.2.0-beta01","sha1":"358cec0205fddd7ebffcb4d905d8e5d86daa4819"},{"version":"3.2.0","sha1":"ace857c31c8040e96ea761b900f6f9286ce92be4"},{"version":"3.3.0-alpha01","sha1":"bb6996778003deaa56801e24d33df3acbda67426"}]},{"package":"espresso-web","versions":[{"version":"3.1.0-alpha1","sha1":"b33e76e189f57e4eac6c24a6e7738501db75f46c"},{"version":"3.1.0-alpha2","sha1":"dca599b7602c87fb623ef617d0a9a23545f21bb9"},{"version":"3.1.0-alpha3","sha1":"2758d9829e74f46cc06184426df46576d833b601"},{"version":"3.1.0-alpha4","sha1":"1973d35a0afbab408cd54dd321f15944268f6d72"},{"version":"3.1.0-beta01","sha1":"606b8a7521c7025e7e2b79e76f262817e2cd6c04"},{"version":"3.1.0-beta02","sha1":"2f5827aa20af919216ed6ba9dbf648ef3db4fcc5"},{"version":"3.1.0","sha1":"961474371bb82ac0ee0f01bd46be28b6d35f4596"},{"version":"3.1.1-alpha01","sha1":"e12a12303cedd096263a3d38cfdc6b0887f621e2"},{"version":"3.1.1-beta01","sha1":"ba8d2555e81b2edd6f7d1f641773d1a754a899aa"},{"version":"3.1.1","sha1":"3492e3496f5c45e14e98ace1a50072674e79adeb"},{"version":"3.1.2-alpha01","sha1":"b9406d9b53ce08bb8f8c8648ab961a61cd0326c5"},{"version":"3.2.0-alpha02","sha1":"c74a4b451ccc0a4ab227dd15e4ea81d8aa217c0c"},{"version":"3.2.0-alpha03","sha1":"db43a57df07f62c29df5383108f1a8a53454e4ed"},{"version":"3.2.0-alpha04","sha1":"747b9d0fd4b87406328e59fa1451a468bc99ebf3"},{"version":"3.2.0-alpha05","sha1":"97fca1501876b719badb2a0fb2759a95bf1355ea"},{"version":"3.2.0-beta01","sha1":"25712930b01dcbafdd36615faf0311d4e9317e0b"},{"version":"3.2.0","sha1":"3e835d126b022ca057e8f48d81ae43e09b9fbc74"},{"version":"3.3.0-alpha01","sha1":"3c9e6a32ff7139025ae34617c994cbe0d8c6f6b7"}]},{"package":"espresso-intents","versions":[{"version":"3.1.0-alpha1","sha1":"341971ef8a305bfce85fccd6bb6165c70567269c"},{"version":"3.1.0-alpha2","sha1":"3b2965b64a089465f1cb6a0ef5c372de279e1e3c"},{"version":"3.1.0-alpha3","sha1":"d6cf4ef46a1c396d970c99e336e1ea562d1aa296"},{"version":"3.1.0-alpha4","sha1":"1cf009ed8fd0dbbaaffba10787418b05aaf6700b"},{"version":"3.1.0-beta01","sha1":"69bcb8cbc10d6a34c4c32c582eb9194ac36581c4"},{"version":"3.1.0-beta02","sha1":"f5976e813b2b9a6384858eec1a2d8c98792236cd"},{"version":"3.1.0","sha1":"ff8f0116e216e7add1e82f1ec3a756932d1df416"},{"version":"3.1.1-alpha01","sha1":"fa2528b20ebabbbd15c483026c60bf7e07bc25ff"},{"version":"3.1.1-beta01","sha1":"7ec9fe520fa71573109be5f8ea35789d7a2d2b2a"},{"version":"3.1.1","sha1":"440bccc70085f41b44c10466a7af41ad94a7df2"},{"version":"3.1.2-alpha01","sha1":"8df127a192f1a607f19c99c0aec33113ae1052ea"},{"version":"3.2.0-alpha02","sha1":"9e71cf275adc334f5116afd7017c03de84e8b285"},{"version":"3.2.0-alpha03","sha1":"7d4e45d18046e5d89918ae70ee3494a5e7d572b3"},{"version":"3.2.0-alpha04","sha1":"4eec7c8b46f6bd76687d78db6e32b72c22a15333"},{"version":"3.2.0-alpha05","sha1":"102a9b8c99fa75f6025b62bd557b1105eb7938db"},{"version":"3.2.0-beta01","sha1":"2263e13915a697cd9120d1f9b27eb4434e77fedf"},{"version":"3.2.0","sha1":"d8bdba97495b45d878a967aff5a2b1f8af30f03f"},{"version":"3.3.0-alpha01","sha1":"1781a92ae443ef4ad0cf5abc16b5543de013dbd2"}]},{"package":"espresso-idling-resource","versions":[{"version":"3.1.0-alpha1","sha1":"cfe3e4b1f044ef7624374fc03e4780c4d47ff121"},{"version":"3.1.0-alpha2","sha1":"b849369a1608b0fc54118cf7bc7b111076284ecf"},{"version":"3.1.0-alpha3","sha1":"72f8779f6d0386ab5d58d8e610fbb13cac5508c0"},{"version":"3.1.0-alpha4","sha1":"3611056d4fe2ce2fc1033917c3dd275309358e73"},{"version":"3.1.0-beta01","sha1":"171be17c56c5e43cffb265dd434cd35ff2e207a7"},{"version":"3.1.0-beta02","sha1":"bf7642a43301472f8f5d9b243063691555bfc33c"},{"version":"3.1.0","sha1":"191856a018859052972376eca0b74a5fbcfae1a6"},{"version":"3.1.1-alpha01","sha1":"de2e94a7d9238dee5f98f2744c929bbbe49ea1cc"},{"version":"3.1.1-beta01","sha1":"db8ab36353d395ae0d874b086b7f22848858991"},{"version":"3.1.1","sha1":"6a73d148333b5b0c967989f8928228bda80824e6"},{"version":"3.1.2-alpha01","sha1":"ebec5c80c2d3d69bf546b4aa713774803bdfd435"},{"version":"3.2.0-alpha02","sha1":"89127ec5bd0971dd7a183762a8452f731e0653c3"},{"version":"3.2.0-alpha03","sha1":"1beb823b95d5e5ab3c68b3acb3de0786bdc75d6b"},{"version":"3.2.0-alpha04","sha1":"11d057e5b463fcf45032d45176f07f2bb27463ec"},{"version":"3.2.0-alpha05","sha1":"5805c12e0bb58bd36104295fc145e691b705ca3c"},{"version":"3.2.0-beta01","sha1":"310c1c245cef4633097ba44ac75449294b2c3a4b"},{"version":"3.2.0","sha1":"8cc2655c53784bb60ed082dbd77753dd1ad5a816"},{"version":"3.3.0-alpha01","sha1":"77a2823ee9bd5d7cd4bf3cd17b6a7f7f42d0c0e5"}]}]},{"group":"androidx.test.espresso.idling","update_time":-1,"packages":[{"package":"idling-net","versions":[{"version":"3.1.0-alpha1","sha1":"1f00795a84d3da24958d834726a2b26a27c3af08"},{"version":"3.1.0-alpha2","sha1":"b66775e7773a84e4f31a25e255ac1019012a1706"},{"version":"3.1.0-alpha3","sha1":"e0779e06d6fca49326e7ea382f3a18f6840c9cc2"},{"version":"3.1.0-alpha4","sha1":"79529632dcf32cd5395fdeecf1dbae202338ac4a"},{"version":"3.1.0-beta01","sha1":"de93e3ae7aba4bd6f0df58e3e250ce5b023695e4"},{"version":"3.1.0-beta02","sha1":"3e014187efc492dacb8bc6f8e849ac6d7478de50"},{"version":"3.1.0","sha1":"4e850964b4ff67d3a7bae0f28242cd2c7185b471"},{"version":"3.1.1-alpha01","sha1":"38db41e4f33336758d4e7d0ce109b74504deb169"},{"version":"3.1.1-beta01","sha1":"ab19583caf03b81a8f59882758e0a83c8ca482b3"},{"version":"3.1.1","sha1":"1dac5c1f56eff1ed841a6e95e943863b8cbbdf57"},{"version":"3.1.2-alpha01","sha1":"f74b82c7f113b1f062cdf29e7d06b3b273bdcba3"},{"version":"3.2.0-alpha02","sha1":"39db3d1af5706bd670f54cd9e324d102ebfade6c"},{"version":"3.2.0-alpha03","sha1":"d2b1ee029ee45d607d341e7418e1a9277d203d68"},{"version":"3.2.0-alpha04","sha1":"e22477d23fcfc286bb703537104434f4bc9e4643"},{"version":"3.2.0-alpha05","sha1":"115c0a15d2a466d0abf32f0598cca2010c652fb2"},{"version":"3.2.0-beta01","sha1":"9119e0ed21fcdf9c54a1fb2ea73d84df37db8fa5"},{"version":"3.2.0","sha1":"deb83b8573c1daf2e83089fe97c62250e4b7e619"},{"version":"3.3.0-alpha01","sha1":"6ba5c81fecf6569c86ad124ddbed355b1b20956d"}]},{"package":"idling-concurrent","versions":[{"version":"3.1.0-alpha1","sha1":"48e7193cca16bd0e9f26f0fad32b450a118db8f2"},{"version":"3.1.0-alpha2","sha1":"6f233f644bc7f318d8302f6bf6c2bfafed851ecc"},{"version":"3.1.0-alpha3","sha1":"69aba9b06625d40bb85fc19d9760814447dae1ad"},{"version":"3.1.0-alpha4","sha1":"e9afaf756fa84804a331977d4d831b51ec295cff"},{"version":"3.1.0-beta01","sha1":"590d95170d2077de05d3c831fc9eef06836a03cf"},{"version":"3.1.0-beta02","sha1":"5c35252024686f3976748852771467ce398acd0f"},{"version":"3.1.0","sha1":"865dcfa44bdbdb4c3121db5f8356218b5a78da56"},{"version":"3.1.1-alpha01","sha1":"4182449fad6d14ff008a19562be9e4966d5097ff"},{"version":"3.1.1-beta01","sha1":"a02435d4a7399bb38f6f73d41d3f578f9e95336e"},{"version":"3.1.1","sha1":"47d99e77cacf1468dcd77a2f179f2e15a6a2cf84"},{"version":"3.1.2-alpha01","sha1":"d2d8bedce7bcd26df805b6dd30640010b0d4c6e0"},{"version":"3.2.0-alpha02","sha1":"ab34e074a911e6674662bdeac0a9bf8ec2c5656"},{"version":"3.2.0-alpha03","sha1":"9cf81e0bdbf1aa2fc31923c31a63add61b56606a"},{"version":"3.2.0-alpha04","sha1":"f717f44992c09e59c9178b96a298b976c5be503b"},{"version":"3.2.0-alpha05","sha1":"95c409ad6d5080eb8ed2f2d7f58655c9f6eb572a"},{"version":"3.2.0-beta01","sha1":"6f90062e8ba8184e25bc7b68fa28a2720b9742df"},{"version":"3.2.0","sha1":"a327d261e69d8c4c8777497b08d5647ea8dd1bab"},{"version":"3.3.0-alpha01","sha1":"2205c896f0f8e6a97554db7b30a43abd3a620d44"}]}]},{"group":"androidx.test.uiautomator","update_time":-1,"packages":[{"package":"uiautomator-v18","versions":[{"version":"2.2.0-alpha1","sha1":"b85c01245d60e5b20fdbb32346cedf675ac778a1"}]},{"package":"uiautomator","versions":[{"version":"2.2.0-alpha3","sha1":"38b0439e8e49d2c742af32b0cf3b3ae8f5ae5f3a"},{"version":"2.2.0-alpha4","sha1":"9ad56c30285af924ae153fb5a4af494326c96fd5"},{"version":"2.2.0-beta01","sha1":"9ebd7fd5aad0e03c13fca692537c88b869c38195"},{"version":"2.2.0-beta02","sha1":"fb8194801af9382d957e9150db35328e111f42a4"},{"version":"2.2.0","sha1":"a9e3b787a45c96754b065af36640a210fc0190cc"}]}]},{"group":"androidx.room","update_time":-1,"packages":[{"package":"room-rxjava2","versions":[{"version":"2.0.0-alpha1","sha1":"e9725f0d242fb09167003e44254342f169f9ec99"},{"version":"2.0.0-beta01","sha1":"6eb98618ffabfb7d5727c8592622d489fd4b709b"},{"version":"2.0.0-rc01","sha1":"c8e2a4d2b12fb0822049d7d9eb76c4432c74d7c2"},{"version":"2.0.0","sha1":"eb5ed70cf44d152ef49adef1ba5fc9c8bc7a8b33"},{"version":"2.1.0-alpha01","sha1":"8a474f6fd47082f870519adb1dc5fd1fad0899e9"},{"version":"2.1.0-alpha02","sha1":"7ca8e63f3fc261d4925d6c21c598ab9b6ab5586a"},{"version":"2.1.0-alpha03","sha1":"2ff0c7f938c889d5909122295b84d846ae618bb1"},{"version":"2.1.0-alpha04","sha1":"69c6d681cdcbd14c733f40f38d991b9eaab2abe5"},{"version":"2.1.0-alpha05","sha1":"b729e0c3eb708e8e48681cee5d831c9745384c28"},{"version":"2.1.0-alpha06","sha1":"ad6b205e1e4765e1615a41280d466bbc08bb6525"},{"version":"2.1.0-alpha07","sha1":"74562b5bc3997848a9bcf177a22efeb126094402"},{"version":"2.1.0-beta01","sha1":"b7e30a19eb352fbb77d6ec87cbd861fc26e60c1a"},{"version":"2.1.0-rc01","sha1":"9b97e0ea33e75565c9754882c58ef62066d9fffe"},{"version":"2.1.0","sha1":"677de5c781f40e6f1bb6644b93bbee6f6431d113"},{"version":"2.2.0-alpha01","sha1":"5439a8b973a7a77b333f0ac45cad93b992487227"}]},{"package":"room-common","versions":[{"version":"2.0.0-alpha1","sha1":"a48ecd3d8726b7f959072dbcfc86bd37b6c8c7b6"},{"version":"2.0.0-beta01","sha1":"ecbcb11279cd5568fa7d86a0a7dc13ba0d8f37c9"},{"version":"2.0.0-rc01","sha1":"bea2cdbd2043710c99e2e1f85040364af04d69a"},{"version":"2.0.0","sha1":"ecbcb11279cd5568fa7d86a0a7dc13ba0d8f37c9"},{"version":"2.1.0-alpha01","sha1":"be2603c40d004562850e5c48f1bd0dd732b0004a"},{"version":"2.1.0-alpha02","sha1":"be2603c40d004562850e5c48f1bd0dd732b0004a"},{"version":"2.1.0-alpha03","sha1":"accf8a6ef15cd5fa08ac6ddc21544e2d2e6888fa"},{"version":"2.1.0-alpha04","sha1":"f03cd934084a81f0e6539228b149b01521ad756e"},{"version":"2.1.0-alpha05","sha1":"b72ab1911bfbaaac7a9f9c337abc23ab4900e728"},{"version":"2.1.0-alpha06","sha1":"b72ab1911bfbaaac7a9f9c337abc23ab4900e728"},{"version":"2.1.0-alpha07","sha1":"b72ab1911bfbaaac7a9f9c337abc23ab4900e728"},{"version":"2.1.0-beta01","sha1":"b72ab1911bfbaaac7a9f9c337abc23ab4900e728"},{"version":"2.1.0-rc01","sha1":"b87765704590bd992ea0d92ac50253a9df7818a0"},{"version":"2.1.0","sha1":"b87765704590bd992ea0d92ac50253a9df7818a0"},{"version":"2.2.0-alpha01","sha1":"f5e3b73a0c2ab5e276e26868e4ce3542baede207"}]},{"package":"room-compiler","versions":[{"version":"2.0.0-alpha1","sha1":"2ce5059d86384e68323a497d059041fd586e45bb"},{"version":"2.0.0-beta01","sha1":"2272443820a5462cb827e94607d70fcd0077e40a"},{"version":"2.0.0-rc01","sha1":"40f2cb82ca59cc9cfb1258a1734e0632ee7ecc4f"},{"version":"2.0.0","sha1":"3782b9a1c39c51632244709a9c318b928298a36c"},{"version":"2.1.0-alpha01","sha1":"f12c1c9c5f2bb3a50b1d8cfe8f97d7f7996417e4"},{"version":"2.1.0-alpha02","sha1":"20470ce7dd18ab0aec586f7e7add40108b54e7ea"},{"version":"2.1.0-alpha03","sha1":"1bb1e1e5539bbb9ee46c4cb4839f34ff87c04f8c"},{"version":"2.1.0-alpha04","sha1":"7583720b7b156c7cc73d116d4bd5aef2dbda9647"},{"version":"2.1.0-alpha05","sha1":"194bacf3c8a32e192964b04e8a55e6f2d1767c0a"},{"version":"2.1.0-alpha06","sha1":"78424fb0d6ac74b267eb841f4991e20d59506f0d"},{"version":"2.1.0-alpha07","sha1":"d66c4b0e1ea29310f4190cf6062f81a046c061c1"},{"version":"2.1.0-beta01","sha1":"d66c4b0e1ea29310f4190cf6062f81a046c061c1"},{"version":"2.1.0-rc01","sha1":"80cbd3abd56f82a4ca2085e64dcd3ecc8bf9fe47"},{"version":"2.1.0","sha1":"80cbd3abd56f82a4ca2085e64dcd3ecc8bf9fe47"},{"version":"2.2.0-alpha01","sha1":"357be9be63f6d57645caef9016afb523e33b09e5"}]},{"package":"room-guava","versions":[{"version":"2.0.0-alpha1","sha1":"3ac5d607d661e49c879633bbb8071bccde1e4584"},{"version":"2.0.0-beta01","sha1":"47779d4f2e38122125fcc85167eb0f214445ba7b"},{"version":"2.0.0-rc01","sha1":"4a2da047130248e65b4106199ffd2932c3302736"},{"version":"2.0.0","sha1":"d5aa1e72cb4474ceae72482081ccf3e9da888e96"},{"version":"2.1.0-alpha01","sha1":"8d987538babb1d022e443d9a31d548be6cdcefb9"},{"version":"2.1.0-alpha02","sha1":"46173d120ab270d0484156993de4f3be42bc18e4"},{"version":"2.1.0-alpha03","sha1":"ccf60ed15899a945879685792eec8ba6aa813cc4"},{"version":"2.1.0-alpha04","sha1":"ffc044ffc5e8323471b180196707c57e679eb82"},{"version":"2.1.0-alpha05","sha1":"613533bdccc1df80bae522b714cd935394d5466c"},{"version":"2.1.0-alpha06","sha1":"8396b52cb6be4ba8f5857433505cfc326e60f1bd"},{"version":"2.1.0-alpha07","sha1":"753e5202bfde67f74847075ca1b27a12a4abf68f"},{"version":"2.1.0-beta01","sha1":"d104b621825094e2ceb091c85950f7be7c0bc7b1"},{"version":"2.1.0-rc01","sha1":"5192a2137204152de7cddda7678f0a98a9badab2"},{"version":"2.1.0","sha1":"650fbde433173815df3e3599fa917fada805cfba"},{"version":"2.2.0-alpha01","sha1":"2a24943bd269e98c42ca19319d8232934821a6c1"}]},{"package":"room-migration","versions":[{"version":"2.0.0-alpha1","sha1":"cc34b05145cc1cf6dee7a3be3b809a5f241c15c2"},{"version":"2.0.0-beta01","sha1":"1f8661aa761ac73d08939dabe3065bc2a3f84ffd"},{"version":"2.0.0-rc01","sha1":"8af359d1291de931a760750eb8ea1012a40bf690"},{"version":"2.0.0","sha1":"1f8661aa761ac73d08939dabe3065bc2a3f84ffd"},{"version":"2.1.0-alpha01","sha1":"34efbd41393940f1a2684c34cb8c8195f18a7f47"},{"version":"2.1.0-alpha02","sha1":"34efbd41393940f1a2684c34cb8c8195f18a7f47"},{"version":"2.1.0-alpha03","sha1":"34efbd41393940f1a2684c34cb8c8195f18a7f47"},{"version":"2.1.0-alpha04","sha1":"3f7c86b7fcc4d5bca3a5633a208db4a4d6036c9"},{"version":"2.1.0-alpha05","sha1":"968634ffd5d1fd2f5e7b2eb430cc07049966cce9"},{"version":"2.1.0-alpha06","sha1":"968634ffd5d1fd2f5e7b2eb430cc07049966cce9"},{"version":"2.1.0-alpha07","sha1":"968634ffd5d1fd2f5e7b2eb430cc07049966cce9"},{"version":"2.1.0-beta01","sha1":"968634ffd5d1fd2f5e7b2eb430cc07049966cce9"},{"version":"2.1.0-rc01","sha1":"968634ffd5d1fd2f5e7b2eb430cc07049966cce9"},{"version":"2.1.0","sha1":"968634ffd5d1fd2f5e7b2eb430cc07049966cce9"},{"version":"2.2.0-alpha01","sha1":"1c87132a6902c12c5783ff1a3877cda99444c50e"}]},{"package":"room-testing","versions":[{"version":"2.0.0-alpha1","sha1":"b888da65f24666aa3bd809f22948e7d13b18ce2b"},{"version":"2.0.0-beta01","sha1":"cadd5f4d643988c040fca0b6d6a5eed8dfeb3017"},{"version":"2.0.0-rc01","sha1":"3edb1d6f3073d55d41d1d47b960d10916b7f2003"},{"version":"2.0.0","sha1":"9391b2c8ae2d5f57027d7bc4ff06ac9b4a587ee6"},{"version":"2.1.0-alpha01","sha1":"b30a0b2d9f37f3ab81e6276c12b7c3a1ce325a04"},{"version":"2.1.0-alpha02","sha1":"46a55e9825d976dfb83d1eb1f6db882f095f55c0"},{"version":"2.1.0-alpha03","sha1":"258f63de62eda9b022132923b0c9b6f018e64e9c"},{"version":"2.1.0-alpha04","sha1":"ff12ddf97afd162e3afd40f177ea82e594ca807f"},{"version":"2.1.0-alpha05","sha1":"1a6fb86f13bf365a0a29c3f280c56ab5f54c6ee3"},{"version":"2.1.0-alpha06","sha1":"a1c86ba9da9ad523695f8d48cade4e5fda159a09"},{"version":"2.1.0-alpha07","sha1":"2e541b8ed1720d17d42bb699f534de29b558612b"},{"version":"2.1.0-beta01","sha1":"32f8966d08bfd08c6be58b3086199638ed38452"},{"version":"2.1.0-rc01","sha1":"b055ccade6782588fecbc8ade070150c7a5907b8"},{"version":"2.1.0","sha1":"145dd679bd1b89b8e1bd3f795557507b70818255"},{"version":"2.2.0-alpha01","sha1":"8f373f9479286a5a61206cce78711d82eaf49109"}]},{"package":"room-runtime","versions":[{"version":"2.0.0-alpha1","sha1":"3e497fd98bdfa803e90334be3662daae8c974ead"},{"version":"2.0.0-beta01","sha1":"837f45157de74d171265398d4fcee65a655ff683"},{"version":"2.0.0-rc01","sha1":"e70a4a36ff8f81b49663c84371137daa82f14da0"},{"version":"2.0.0","sha1":"f716fa8846f0d4d72025dfbda34d3a6d37dce3a2"},{"version":"2.1.0-alpha01","sha1":"30d8d0a93ccf140c424132792a5f08bf8acfe7ef"},{"version":"2.1.0-alpha02","sha1":"5b3ba44099f6a82441202af7db1d96f617c3ee38"},{"version":"2.1.0-alpha03","sha1":"ebc5a3ffdebb9639c64a18e36175cf7a93ef5a8e"},{"version":"2.1.0-alpha04","sha1":"f90140181673b58a906b6999385812a84532b76f"},{"version":"2.1.0-alpha05","sha1":"7d1213207225d5acd5bf6d64be0faf9d78a9fd2c"},{"version":"2.1.0-alpha06","sha1":"6c90324534503a30e325efe79a7e82e3e5cc518"},{"version":"2.1.0-alpha07","sha1":"ab34c47e1c1f2d8f1a264c112cc281ee2b8e2c66"},{"version":"2.1.0-beta01","sha1":"35efdeee814628c5acbc61a5ebcbcaecb21635bd"},{"version":"2.1.0-rc01","sha1":"32e57464749d0c2033b6d31a5703683ddc43a94d"},{"version":"2.1.0","sha1":"1f534141feec4f659da80397f53fc97a055aa5ea"},{"version":"2.2.0-alpha01","sha1":"49cad8313dbd37c54514407b1572f43f50836510"}]},{"package":"room-coroutines","versions":[{"version":"2.1.0-alpha03","sha1":"9f404ded9f1af050c50b7fd4600fc063c4223e11"},{"version":"2.1.0-alpha04","sha1":"608d3b6682b4e59624bd531e50d868ba91454569"}]},{"package":"room-ktx","versions":[{"version":"2.1.0-alpha05","sha1":"98d25fc401b7f80b0fd1b21f4d24bda520c210b6"},{"version":"2.1.0-alpha06","sha1":"9845404b9e22cde6c527bd20ee36249cd3d63fa"},{"version":"2.1.0-alpha07","sha1":"b868602eefd265b4f351cea61a2f5e6ad8c9fa73"},{"version":"2.1.0-beta01","sha1":"55e90b65c653a7d63a261b544154101e1565c262"},{"version":"2.1.0-rc01","sha1":"4db9b86c4578a148b35df5c9d1e21974e078d5ac"},{"version":"2.1.0","sha1":"dfd359b70c15c791f45f0ccb187fb6933b92070b"},{"version":"2.2.0-alpha01","sha1":"85e4cc11bd069b1813838621cd56730b41655970"}]}]},{"group":"androidx.paging","update_time":-1,"packages":[{"package":"paging-common","versions":[{"version":"2.0.0-alpha1","sha1":"16c11237328565846981bbbc78f749b8e7f4128e"},{"version":"2.0.0-beta01","sha1":"98c255306a63799fe396a2b564f4e4a3652e9c41"},{"version":"2.0.0-rc01","sha1":"cb80c0f98bfb6241ba965c0e0921a2dcf243a523"},{"version":"2.0.0","sha1":"2137b50ea241c4ccaa7378479cfcb15c4131ec5c"},{"version":"2.1.0-alpha01","sha1":"237a3af3a88b21bd471d2a4d49a69d82dc73c43f"},{"version":"2.1.0-beta01","sha1":"237a3af3a88b21bd471d2a4d49a69d82dc73c43f"},{"version":"2.1.0-rc01","sha1":"99c02e0f76cad36752e87155703eab8d28e8c7d1"},{"version":"2.1.0","sha1":"237a3af3a88b21bd471d2a4d49a69d82dc73c43f"}]},{"package":"paging-runtime","versions":[{"version":"2.0.0-alpha1","sha1":"22410de681c90239a72bb7473433da2af18a7f46"},{"version":"2.0.0-beta01","sha1":"616eb21f2371a894737ab728a1f4af8fed90d2fa"},{"version":"2.0.0-rc01","sha1":"2a89aa5356c58d016779c4b07e4249bf5f239b42"},{"version":"2.0.0","sha1":"9532a638eba10b018ba90a4e2886c95a0fd4767e"},{"version":"2.1.0-alpha01","sha1":"78b804b5e5f4e64db499d20a03f41db3bcb52d50"},{"version":"2.1.0-beta01","sha1":"9ffdc130d11b15fea0607a8bbe60eb1212ce2c39"},{"version":"2.1.0-rc01","sha1":"e3a93b9c6764c51da9203324fa099859629b66c4"},{"version":"2.1.0","sha1":"220d2518c8791103600d10712401c9951a723f11"}]},{"package":"paging-rxjava2","versions":[{"version":"1.0.0-alpha1","sha1":"97cd04ce3f54b931617b70967e051f37e54d85df"},{"version":"2.0.0-alpha1","sha1":"d3b3e3168fafb92f53f884df27a9e5195edbd56a"},{"version":"2.0.0-beta01","sha1":"dc199ef49bd8a89e3fba02aae6c3f573fe2a1df1"},{"version":"2.0.0-rc01","sha1":"a00849e0487b688bef791b16ebabdc1175f8c8f"},{"version":"2.0.0","sha1":"6461c64a0e9ab610d5234e474166ce0e5db6a93b"},{"version":"2.1.0-alpha01","sha1":"be7763454f725eabfb1d70cb878d83bcca2d6a87"},{"version":"2.1.0-beta01","sha1":"4deb9a01d057ba5616cdfe98fd98ca0db2c9001f"},{"version":"2.1.0-rc01","sha1":"54901911f007f2caed03be71c199b42b0dbb4424"},{"version":"2.1.0","sha1":"2da0e1678c35918c4e690f6a3fe8a282582c4800"}]},{"package":"paging-runtime-ktx","versions":[{"version":"2.1.0-alpha01","sha1":"7b2f804ce91ae14736cd19336e5ebc420ddb4936"},{"version":"2.1.0-beta01","sha1":"4e72e161554a46865034020216ff6ed9d112de0e"},{"version":"2.1.0-rc01","sha1":"8f9dce7602cb289e4a48104c943b78f6982b8577"},{"version":"2.1.0","sha1":"72a20d08003fb4e13a21a19ec786ce4220da7677"}]},{"package":"paging-rxjava2-ktx","versions":[{"version":"2.1.0-alpha01","sha1":"b94b71b8d7408e655f1dfb049328dd9248620e24"},{"version":"2.1.0-beta01","sha1":"ced8483f5cc48f940481ba638b59d55d16d0945a"},{"version":"2.1.0-rc01","sha1":"816d4749d24fe635dd396dd3e2433fc6400dc1b6"},{"version":"2.1.0","sha1":"51a4eb19903a3026bc46a221a6344daecc73b66f"}]},{"package":"paging-common-ktx","versions":[{"version":"2.1.0-alpha01","sha1":"3acd13bd21e696857fe39989070c1018ec79166a"},{"version":"2.1.0-beta01","sha1":"3acd13bd21e696857fe39989070c1018ec79166a"},{"version":"2.1.0-rc01","sha1":"48762908784a70c52e081a6c4771f505b6585cff"},{"version":"2.1.0","sha1":"3acd13bd21e696857fe39989070c1018ec79166a"}]}]},{"group":"androidx.lifecycle","update_time":-1,"packages":[{"package":"lifecycle-reactivestreams-ktx","versions":[{"version":"2.0.0-alpha1","sha1":"fd7137c6ae72f6c5d5cfd97e3c1ae2e4eec5684"},{"version":"2.0.0-beta01","sha1":"c20ba910e2eccdf6c675c60c3f3f5dea46110beb"},{"version":"2.0.0-rc01","sha1":"c73f11138de7733c9b4e317679418a2bbe656eaf"},{"version":"2.0.0","sha1":"366ed0ba5a87eba58baa513981477c74b7d80102"},{"version":"2.1.0-alpha01","sha1":"bc93b93636f430c10463309d0104cc42f1d46fc9"},{"version":"2.1.0-alpha02","sha1":"ca3fc4eb1ea00f101f3027d600085f4eb772ba6b"},{"version":"2.1.0-alpha03","sha1":"5a9e7a576343b678750bf6ffc3c4a43a0913339c"},{"version":"2.1.0-alpha04","sha1":"4db073313ea27cd2c939b702efbec441df8e8dee"},{"version":"2.1.0-beta01","sha1":"81c6c7f5aea852910445624e8a9b90192c241030"},{"version":"2.1.0-rc01","sha1":"f333793a181b9dfc756106870390ec81713294f6"},{"version":"2.2.0-alpha01","sha1":"cac396434596e18e722b6bcdec43d0bbadf0f8ed"},{"version":"2.2.0-alpha02","sha1":"af50a1d0666d975a4bb1cd0be13963aa84b3f8a3"}]},{"package":"lifecycle-viewmodel","versions":[{"version":"2.0.0-alpha1","sha1":"7b3119f7975f1f9c83a3e2afcf46eccbdae88659"},{"version":"2.0.0-beta01","sha1":"794c5423a88a788d69b9f4a3f3305a5d431422d5"},{"version":"2.0.0-rc01","sha1":"40b06e3d82535f381995968d6bef83fe4aeb9b0"},{"version":"2.0.0","sha1":"6417c576c458137456d996914c50591e7f4acc24"},{"version":"2.1.0-alpha01","sha1":"74a485445412d345e97c651c52517b50efc5222e"},{"version":"2.1.0-alpha02","sha1":"8540e46241d2f2fb08d3de0451c8547c4ea52596"},{"version":"2.1.0-alpha03","sha1":"e8599782f3d60a149865d0577d8af70691ac44c1"},{"version":"2.1.0-alpha04","sha1":"f93fcea777f6af78ff36d3a69b94d593241f8502"},{"version":"2.1.0-beta01","sha1":"5021f39151638d9847e87f076d4dc85a68dcc60d"},{"version":"2.1.0-rc01","sha1":"b55fd435828b78efe4866ed28bb922161597c7ac"},{"version":"2.2.0-alpha01","sha1":"be5d63c6fcf3ff5e6ed2c886d778c7833646fe96"},{"version":"2.2.0-alpha02","sha1":"fa4cf9e388fc92cc63a1ce22badf25362f268434"}]},{"package":"lifecycle-process","versions":[{"version":"2.0.0-alpha1","sha1":"1e028b53fa335a3704faa9e047e28aeea1c205bc"},{"version":"2.0.0-beta01","sha1":"a63b1a9910ba153f9cfa0829fdb18342cdb1cbfc"},{"version":"2.0.0-rc01","sha1":"59de62eb64f24adc33b1891ce807f3743131d37"},{"version":"2.0.0","sha1":"93dab22e0a3f64988d662803af87398c53ffd3af"},{"version":"2.1.0-alpha01","sha1":"a432e9ea122cb854361342cd1f50af9ccf38fbd8"},{"version":"2.1.0-alpha02","sha1":"d484e56d57cb8b89489be25e0a7d8f123d679e6e"},{"version":"2.1.0-alpha03","sha1":"710b93f08a448c2cb325d5e6fb576b6e0e19fb3b"},{"version":"2.1.0-alpha04","sha1":"55a1150132c29d3508aaa8d2695dfce2d26ff0d4"},{"version":"2.1.0-beta01","sha1":"69f9619b85304824d3ef505b802c13ea89f29f81"},{"version":"2.1.0-rc01","sha1":"4f134cc3685e9ddb5f1b4200c7a3b4c4c35e955c"},{"version":"2.2.0-alpha01","sha1":"18bd04f6905c74698b6a38e228d870799504aa0e"},{"version":"2.2.0-alpha02","sha1":"f18ff81e2fd0a638919ebdd558e595e5b4dc0795"}]},{"package":"lifecycle-viewmodel-ktx","versions":[{"version":"2.0.0-alpha1","sha1":"4658f2a7cbf260b87fee5a48b3ec45abdcfda387"},{"version":"2.0.0-beta01","sha1":"356a552bb6b607f02a70041c3bbec91bf797e313"},{"version":"2.0.0-rc01","sha1":"f3da920a2dd072ca6eb83f6df464b2741f784a1c"},{"version":"2.0.0","sha1":"f21d8cbc59c1f9a63b0bb0a09a0e058aba74489f"},{"version":"2.1.0-alpha01","sha1":"7de614945767b03b35c75f6d154a884fd9bb6ccc"},{"version":"2.1.0-alpha02","sha1":"b53edd0dc4e14356b5356dfd59ff46b2568c1bcc"},{"version":"2.1.0-alpha03","sha1":"8ebf0a6c5070dfb31875305facc8be78fb2b3a8f"},{"version":"2.1.0-alpha04","sha1":"d3ccbdd33bae7fda4ba795abf4bfdb4b4a57e386"},{"version":"2.1.0-beta01","sha1":"1ddfc1895a347a2099a3aacb1d2b97c0385ebeb"},{"version":"2.1.0-rc01","sha1":"5e2ddf780c6433018cabc638507a1a8b6f953dc0"},{"version":"2.2.0-alpha01","sha1":"e7bace665a887be9f5e137808908f4bf94f0f8dd"},{"version":"2.2.0-alpha02","sha1":"22025b7ff3421a0de34313229387e5f13c336d28"}]},{"package":"lifecycle-reactivestreams","versions":[{"version":"2.0.0-alpha1","sha1":"e56baea30b4e6cb6e4f33bb1b0bf17ec2fb936f1"},{"version":"2.0.0-beta01","sha1":"e9594f01054eeb058ee176140d9729dd84077e6f"},{"version":"2.0.0-rc01","sha1":"6997a5bb08ef57ceb2754f0ea9eeace51e34ecc0"},{"version":"2.0.0","sha1":"641764a49231610dfd0bf18a3d4f9d1b55303baf"},{"version":"2.1.0-alpha01","sha1":"40cb4a976688b339c63b92b6a6d6ce3e7eed1523"},{"version":"2.1.0-alpha02","sha1":"1a10107ebbc75bf64847cfce2db5dc86f7136e40"},{"version":"2.1.0-alpha03","sha1":"58a77607579027a77fe7bdffb483c7f25d3622a0"},{"version":"2.1.0-alpha04","sha1":"f3ecc3af15b5af80e07cdca2cbad28d1070459e2"},{"version":"2.1.0-beta01","sha1":"526f352fb8837d8ddfcf6c9f51608deaea66935d"},{"version":"2.1.0-rc01","sha1":"3597a3a356779729b70dd890a57e316ae347182e"},{"version":"2.2.0-alpha01","sha1":"b7636c276e6e08b07505012625f9e138734d1c64"},{"version":"2.2.0-alpha02","sha1":"d7d37a462049c1600f0b33b2d033ad5cda1b8e6c"}]},{"package":"lifecycle-common-java8","versions":[{"version":"2.0.0-alpha1","sha1":"52cb9efc663d8bebc82f8979a7da30938bd05611"},{"version":"2.0.0-beta01","sha1":"52cb9efc663d8bebc82f8979a7da30938bd05611"},{"version":"2.0.0-rc01","sha1":"4849da8b6d82fbc405c7364a5328752869928a20"},{"version":"2.0.0","sha1":"52cb9efc663d8bebc82f8979a7da30938bd05611"},{"version":"2.1.0-alpha01","sha1":"7cead5fdbbc638272d0f82d50c57d052e6fd6d4e"},{"version":"2.1.0-alpha02","sha1":"cd3478503da69b1a7e0319bd2d1389943db9b364"},{"version":"2.1.0-alpha03","sha1":"cd3478503da69b1a7e0319bd2d1389943db9b364"},{"version":"2.1.0-alpha04","sha1":"cd3478503da69b1a7e0319bd2d1389943db9b364"},{"version":"2.1.0-beta01","sha1":"cd3478503da69b1a7e0319bd2d1389943db9b364"},{"version":"2.1.0-rc01","sha1":"cd3478503da69b1a7e0319bd2d1389943db9b364"},{"version":"2.2.0-alpha01","sha1":"cd3478503da69b1a7e0319bd2d1389943db9b364"},{"version":"2.2.0-alpha02","sha1":"cd3478503da69b1a7e0319bd2d1389943db9b364"}]},{"package":"lifecycle-extensions","versions":[{"version":"2.0.0-alpha1","sha1":"58509e577043acb48993f401583ae1267ca0c3e1"},{"version":"2.0.0-beta01","sha1":"db35906092c97d9d8d857378b2dbb93ddb8ce6e2"},{"version":"2.0.0-rc01","sha1":"c213af2fee0fea4811bbf015c9ade3dcf23b6fb9"},{"version":"2.0.0","sha1":"79e74ad99f7343ede522cb3b3bd5ce729c3c94ea"},{"version":"2.1.0-alpha01","sha1":"ef106d389266acc8c874d73e4c960420f5ed8139"},{"version":"2.1.0-alpha02","sha1":"2a242fb623e9cb7769fcb83e10510ace9b291413"},{"version":"2.1.0-alpha03","sha1":"7947268711995489776c001b13bd18a330f03f77"},{"version":"2.1.0-alpha04","sha1":"79a843ce328c9373edcc5d2c6f783f01925da585"},{"version":"2.1.0-beta01","sha1":"3be56e51647c00009bfe8d2c0ea736aa5930c4c6"},{"version":"2.1.0-rc01","sha1":"34b98c4bfc96bce963178b623f63bcdf8ebd096b"},{"version":"2.2.0-alpha01","sha1":"bd905e9919b57715e32dec9178e6cafa4e446d77"},{"version":"2.2.0-alpha02","sha1":"7b34799c374e36dccb9fe6989ebbdf4dcabe6409"}]},{"package":"lifecycle-livedata-core","versions":[{"version":"2.0.0-alpha1","sha1":"150f8d0a7cb4e1e71629d712f621794ceb89843"},{"version":"2.0.0-beta01","sha1":"2b68eae32e9dfd6b40f34fff0554864f6d896eb6"},{"version":"2.0.0-rc01","sha1":"8b2a899dec520739dce0c611f1c04c6655719290"},{"version":"2.0.0","sha1":"1a7cee84b43fa935231b016f0665cd56a72fa9db"},{"version":"2.1.0-alpha01","sha1":"15f2dbb7e91e8da902cc26d7da132b7ccec4a7a0"},{"version":"2.1.0-alpha02","sha1":"2aa1cb8b189ab899989fb90e86c95417fc6f827e"},{"version":"2.1.0-alpha03","sha1":"a8e50cd9b238b04ba21525cdc11fe70fe063bb6b"},{"version":"2.1.0-alpha04","sha1":"e90ad9bd2dfc5ef8818dde2f3950e48358675bcd"},{"version":"2.1.0-beta01","sha1":"d0181903af9425bc5eeb675b5df83ea3b7105459"},{"version":"2.1.0-rc01","sha1":"d28d2ef0f9e901a1cb828c7425f8747c49916a5d"},{"version":"2.2.0-alpha01","sha1":"d4ffb5c713fbedd9a63ea35b1a38b4c6a7674b90"},{"version":"2.2.0-alpha02","sha1":"325b930dec24ec35acda1244f8c6015ce944373c"}]},{"package":"lifecycle-runtime","versions":[{"version":"2.0.0-alpha1","sha1":"aba164beda63545fee943b58d6f4fa088d9f02eb"},{"version":"2.0.0-beta01","sha1":"94f8500a1283cb3980d2a5381b0145b99bfbc799"},{"version":"2.0.0-rc01","sha1":"887509eb47c3bd4b3a03bbe457518530894d8202"},{"version":"2.0.0","sha1":"ea27e9e79e9a0fbedfa4dbbef5ddccf0e1d9d73f"},{"version":"2.1.0-alpha01","sha1":"9b62fc60d36f29940be7d31a7c91f77f35eb0b79"},{"version":"2.1.0-alpha02","sha1":"898246a08e0879b6aa2f2607de63d0f6d1232c33"},{"version":"2.1.0-alpha03","sha1":"4d84f60572450631b2ca3b47962fdd2e0b5d59d8"},{"version":"2.1.0-alpha04","sha1":"ad1a05e9c2cad13ae3df78c3a2b05b5cf16d80e5"},{"version":"2.1.0-beta01","sha1":"d41e13bec5ceb69fb63cf0e8dea88e5750e47089"},{"version":"2.1.0-rc01","sha1":"9adcac4dbf51391798e5dbeb1c414408d7ff6558"},{"version":"2.2.0-alpha01","sha1":"16a2aa8c56b4cc4341642ce8e5fd3b664d2ae3d3"},{"version":"2.2.0-alpha02","sha1":"6f18a638333a610ef135dc4866859c3984ca2ad9"}]},{"package":"lifecycle-compiler","versions":[{"version":"2.0.0-alpha1","sha1":"70d2297dac3eca404605f1a6f10630ac0c012e1f"},{"version":"2.0.0-beta01","sha1":"449f9233997686d3c5cbcd4182c68f1479cef"},{"version":"2.0.0-rc01","sha1":"5a174ca0b5199af3ab26920867932fdfd3543995"},{"version":"2.0.0","sha1":"449f9233997686d3c5cbcd4182c68f1479cef"},{"version":"2.1.0-alpha01","sha1":"877af952e28d886fa1f51f1f0801bb40f43d918d"},{"version":"2.1.0-alpha02","sha1":"e9044f60c5d85aa2c7dfc0409169c2a35ae640d4"},{"version":"2.1.0-alpha03","sha1":"e9044f60c5d85aa2c7dfc0409169c2a35ae640d4"},{"version":"2.1.0-alpha04","sha1":"e9044f60c5d85aa2c7dfc0409169c2a35ae640d4"},{"version":"2.1.0-beta01","sha1":"63850bdedc5c71353871d262f49ac73dc2c5669e"},{"version":"2.1.0-rc01","sha1":"63850bdedc5c71353871d262f49ac73dc2c5669e"},{"version":"2.2.0-alpha01","sha1":"63850bdedc5c71353871d262f49ac73dc2c5669e"},{"version":"2.2.0-alpha02","sha1":"8ceac9d208ec3cb888f08651a7c47092d6363ac8"}]},{"package":"lifecycle-livedata","versions":[{"version":"2.0.0-alpha1","sha1":"d946678f9028e1852bf3a3079c007877d1ff2093"},{"version":"2.0.0-beta01","sha1":"6a343943f7a8dcabba1253ff7b66a43351896c58"},{"version":"2.0.0-rc01","sha1":"e35e6fa038c86d842340d422f1f61f910505239a"},{"version":"2.0.0","sha1":"c17007cd0b21d6401910b0becdd16c438c68a9af"},{"version":"2.1.0-alpha01","sha1":"8284357541f5c05df195241a238e3475a088a038"},{"version":"2.1.0-alpha02","sha1":"6514565c675efd888b533b6369f9cbaee74c9045"},{"version":"2.1.0-alpha03","sha1":"5fc72fc6c282c1fd4559f891c3783871642bb8e7"},{"version":"2.1.0-alpha04","sha1":"a6eeffb9a59a93065acc48760fd25acb50ad9b03"},{"version":"2.1.0-beta01","sha1":"5607cd9369e4cb16e8355976a02c9d90f3d7fca8"},{"version":"2.1.0-rc01","sha1":"8f8390eeb32bbf35c5919d5a0581adef8b9270ff"},{"version":"2.2.0-alpha01","sha1":"ada117f58c0b0f9eac6746e776a20c23788b5f95"},{"version":"2.2.0-alpha02","sha1":"4d595de9bf2133f6f6b58f322f50e37c9369f6f2"}]},{"package":"lifecycle-common","versions":[{"version":"2.0.0-alpha1","sha1":"281e3b3e30fce3be217f7271a77d9bb36ed2f44b"},{"version":"2.0.0-beta01","sha1":"e070ffae07452331bc5684734fce6831d531785c"},{"version":"2.0.0-rc01","sha1":"5a3ff8721477d3d67070a6ae0f342bdc0b3dff3"},{"version":"2.0.0","sha1":"e070ffae07452331bc5684734fce6831d531785c"},{"version":"2.1.0-alpha01","sha1":"b0215df0b616200b75b78f30586a6090642d92f6"},{"version":"2.1.0-alpha02","sha1":"6053cb532d61ab5ce57ac070c50cfdfe9389a5de"},{"version":"2.1.0-alpha03","sha1":"c67e7807d9cd6c329b9d0218b2ec4e505dd340b7"},{"version":"2.1.0-alpha04","sha1":"c67e7807d9cd6c329b9d0218b2ec4e505dd340b7"},{"version":"2.1.0-beta01","sha1":"c67e7807d9cd6c329b9d0218b2ec4e505dd340b7"},{"version":"2.1.0-rc01","sha1":"c67e7807d9cd6c329b9d0218b2ec4e505dd340b7"},{"version":"2.2.0-alpha01","sha1":"c67e7807d9cd6c329b9d0218b2ec4e505dd340b7"},{"version":"2.2.0-alpha02","sha1":"4ef09a745007778eef83b92f8f23987a8ea59496"}]},{"package":"lifecycle-service","versions":[{"version":"2.0.0-alpha1","sha1":"f3b0f8320394f1a4c3c10369348d2876fa27eac0"},{"version":"2.0.0-beta01","sha1":"89ae46adcd04a21c203d614a0d1082c193f2f151"},{"version":"2.0.0-rc01","sha1":"880ef55d5be94b8d28a11ccbca655cc56a941447"},{"version":"2.0.0","sha1":"986ce80882b8e2ab1095a32088538ea9343747c2"},{"version":"2.1.0-alpha01","sha1":"17827fb30d711c889b649a0df6b368a94bef4f20"},{"version":"2.1.0-alpha02","sha1":"7b1f725c0be4bd07774ccda80091c448a40184ef"},{"version":"2.1.0-alpha03","sha1":"1663ff60abe5f5fb19bf961a1b6d9a7ebedaa45f"},{"version":"2.1.0-alpha04","sha1":"cf29385ca2159e49858213788a4ab005d2650b1"},{"version":"2.1.0-beta01","sha1":"fed4c82ee2b301c4b8e09bc8ad9d0f34b7fed216"},{"version":"2.1.0-rc01","sha1":"11a9bf26cbd761ae6a6b5d3bd6673bcc7eb2a343"},{"version":"2.2.0-alpha01","sha1":"485bdd5410db03cd806ceac1e0b2ba7504208014"},{"version":"2.2.0-alpha02","sha1":"19deca852f09e41925896f72f4b718ea18e2b7b1"}]},{"package":"lifecycle-livedata-ktx","versions":[{"version":"2.1.0-alpha01","sha1":"f3dd5eab7fc117cd1bb39eff801f5e875f7524c2"},{"version":"2.1.0-alpha02","sha1":"814ec14bb4327d48611e6ec952716c8970825924"},{"version":"2.1.0-alpha03","sha1":"5aee83b4574f5157dfd191e625c9f74abec10738"},{"version":"2.1.0-alpha04","sha1":"7943505eb4fa5a78a1cc5f2585b9998edc10f66c"},{"version":"2.1.0-beta01","sha1":"7362fd4f789be72714d8d94d1b702f0bbf9d3523"},{"version":"2.1.0-rc01","sha1":"628dcab96deb231ff3220ace33165de0ec94555f"},{"version":"2.2.0-alpha01","sha1":"8199ccae6621ab88c5b2139361a28c7e395326f"},{"version":"2.2.0-alpha02","sha1":"f377b1c67fe3f6e83da5ed54c751798411da3ad0"}]},{"package":"lifecycle-livedata-core-ktx","versions":[{"version":"2.1.0-alpha01","sha1":"d675a2d6f5616ce84a90179abc2202a9a3f50f1d"},{"version":"2.1.0-alpha02","sha1":"54fc6ddffc495d74c5fd168cc370673d6e5b1a00"},{"version":"2.1.0-alpha03","sha1":"6ccbb077973711b67adeb5e273c5dcf04d018b82"},{"version":"2.1.0-alpha04","sha1":"236e118a57693653f722714817e806d677a137d5"},{"version":"2.1.0-beta01","sha1":"328988f8d5110ccff399cc0698078f46c072e56f"},{"version":"2.1.0-rc01","sha1":"5f8f9b6faa73fa51e2efebcde0b61b9440ef6cf"},{"version":"2.2.0-alpha01","sha1":"14d61dccb4823cefe165c9cf62e524e0e068c679"},{"version":"2.2.0-alpha02","sha1":"2b58d27c148c7a4c685c74ebe1c9f1f9738b9042"}]},{"package":"lifecycle-viewmodel-savedstate","versions":[{"version":"1.0.0-alpha01","sha1":"c9f0debca6129738bfed35e1de0edd70e90b8d99"},{"version":"1.0.0-alpha02","sha1":"d5f00b2b938e05e1a3659c8f92ed0a1d83f7d226"}]},{"package":"lifecycle-runtime-ktx","versions":[{"version":"2.2.0-alpha01","sha1":"8cbf9757c1e2688b4affac5f6d4a85ccb151d391"},{"version":"2.2.0-alpha02","sha1":"d7f6e749fd6242bbe00a8e9c3d36b04a23038973"}]}]},{"group":"androidx.sqlite","update_time":-1,"packages":[{"package":"sqlite-framework","versions":[{"version":"2.0.0-alpha1","sha1":"31a15a339fa35b56502d70b56c9d294dfc802d1a"},{"version":"2.0.0-beta01","sha1":"1c2eaf6577fe7aefdd96f50bbea2b5b91a0ffe4e"},{"version":"2.0.0-rc01","sha1":"d6066893af620a862411c80968f51b19d42ad65"},{"version":"2.0.0","sha1":"7fcc2e9b36eca17936fc10629d52d00649c68b1d"},{"version":"2.0.1","sha1":"86a70973f9705d1bca5e947ae80d361136ba0a3a"}]},{"package":"sqlite","versions":[{"version":"2.0.0-alpha1","sha1":"958b96ae6876d31af9cead19b549404f1ea45e83"},{"version":"2.0.0-beta01","sha1":"a84ca17bf7a9c41a63e6ddd9951f5b85875abe13"},{"version":"2.0.0-rc01","sha1":"3b57628751f75843f4e80803c35f90823b36302c"},{"version":"2.0.0","sha1":"eabae6b64c87ae3c383f3114b427b9abe4dea1f5"},{"version":"2.0.1","sha1":"f6eecc9db98b1888fd97d1cf7f83fda41ba28148"}]},{"package":"sqlite-ktx","versions":[{"version":"2.0.0-alpha1","sha1":"a2e5717c80d0b8447ef191cf9f2bb79a461dd57b"},{"version":"2.0.0-beta01","sha1":"a436df8715ab6924c73a8dc068a63cb588c88c34"},{"version":"2.0.0-rc01","sha1":"c737ff3dbb5d5eda414e2c5f078e5ccecec72237"},{"version":"2.0.0","sha1":"a73e0ee19f9d9388f641079941dadae3feb05180"},{"version":"2.0.1","sha1":"c8767ff087871a80a06e68219bdbbdb546d69f2a"}]}]},{"group":"androidx.arch.core","update_time":-1,"packages":[{"package":"core-common","versions":[{"version":"2.0.0-alpha1","sha1":"d7ef666e361bc32dbc3902f2025a3001798a3c96"},{"version":"2.0.0-beta01","sha1":"bb21b9a11761451b51624ac428d1f1bb5deeac38"},{"version":"2.0.0-rc01","sha1":"b8d58af4adf433117dc96446040f31a0a50fc3fd"},{"version":"2.0.0","sha1":"bb21b9a11761451b51624ac428d1f1bb5deeac38"},{"version":"2.0.1","sha1":"3e8d84e4fe526be0ca1832a518bf323e729a79fd"},{"version":"2.1.0-alpha01","sha1":"52a1b1ca4523c8544b052af470485100183a4303"},{"version":"2.1.0-alpha02","sha1":"53ae30da9c209e6c62655bb10022cd5f5043dce3"},{"version":"2.1.0-beta01","sha1":"b3152fc64428c9354344bd89848ecddc09b6f07e"},{"version":"2.1.0-rc01","sha1":"b3152fc64428c9354344bd89848ecddc09b6f07e"}]},{"package":"core-runtime","versions":[{"version":"2.0.0-alpha1","sha1":"c49c8d02777727aa2503811730cecc5d91a53587"},{"version":"2.0.0-beta01","sha1":"253ce1b15c63404a159b382981e13841773a3ae1"},{"version":"2.0.0-rc01","sha1":"9e9f6f3e8e2d21b0b23af2f57d5afa70c54f56cf"},{"version":"2.0.0","sha1":"c5be9edf9ca9135a465d23939f6e7d0e1cf90b41"},{"version":"2.0.1-alpha01","sha1":"3753b829cf75589f4a0cfaf336dc66baa94b216d"},{"version":"2.0.1","sha1":"5843359688fbf06ac0f022a4f453c9d79e6c9f2c"},{"version":"2.1.0-alpha01","sha1":"22fe3aa0574a9fa82c197afc558d4757bc735a5"},{"version":"2.1.0-alpha02","sha1":"8bd16589f168bbe467264f557b22e16ecf75e9d7"},{"version":"2.1.0-beta01","sha1":"1662261e31262c2a972f8efa2f5cddda043e5aa3"},{"version":"2.1.0-rc01","sha1":"27e6b5d7e0fadd58a53b2ddb8054480bf2162fd5"}]},{"package":"core-testing","versions":[{"version":"2.0.0-alpha1","sha1":"5fe1d949bd52484e2a7d263e0def3452645aa558"},{"version":"2.0.0-beta01","sha1":"1f73940f9c5879e1d83d4da3442ed60de97be3ef"},{"version":"2.0.0-rc01","sha1":"118d944b814a29b3dcff6884b590cc73d4e20b8f"},{"version":"2.0.0","sha1":"c26337f33af37f71ade27c00cb536b3cd3575198"},{"version":"2.0.1","sha1":"87b783c9b48a9235094a8985c9c5b58d0f714cdd"},{"version":"2.1.0-alpha01","sha1":"2cc8573002fb2de36e3a3a7c9133284ceeae3548"},{"version":"2.1.0-alpha02","sha1":"364f557af88c4e4fb91339ae7a422dce216fc4ac"},{"version":"2.1.0-beta01","sha1":"5ba8740e6ac15079934226fb5fa3977158b560b8"},{"version":"2.1.0-rc01","sha1":"a7c0a0d17f39632d3bad6b959b35862ff6a21922"}]}]},{"group":"android.arch.work","update_time":-1,"packages":[{"package":"work-runtime-ktx","versions":[{"version":"1.0.0-alpha01","sha1":"8a99ec6d572cbe6fa7125a14783c5b34d02ed06d"},{"version":"1.0.0-alpha02","sha1":"ff7288e41cd3ed117514ccd0c2f8a2960d5627f4"},{"version":"1.0.0-alpha03","sha1":"3aa52160059521bc0c3af865ef063241a4b2728a"},{"version":"1.0.0-alpha04","sha1":"b5cc673c91c709ae84e7b2ee5d817ba581c4b414"},{"version":"1.0.0-alpha05","sha1":"4f82930436b766e2ebd652243be838d90e0bd606"},{"version":"1.0.0-alpha06","sha1":"104ce2e76c74e91872e33ec0d72423ef3045723c"},{"version":"1.0.0-alpha07","sha1":"17d34acfa67519da26640dae07e19a5af716804a"},{"version":"1.0.0-alpha08","sha1":"c18390c9f41dd3ca2ed15adee28b2d1ad447265c"},{"version":"1.0.0-alpha09","sha1":"f17657ab8484d4cde5321622917a1f3e390b7b5c"},{"version":"1.0.0-alpha10","sha1":"ec1cc11e150e4252b0af48d8ef444aa5964bec0e"},{"version":"1.0.0-alpha11","sha1":"ddabe77b055cbedf7fdf8e50e4064b1c4d721dec"},{"version":"1.0.0-alpha12","sha1":"30b0fdd2e0db577621fb9c09d6e1d8a27396aed9"},{"version":"1.0.0-alpha13","sha1":"fd4c11737217342c58ea8e1e7289f07fc3f3c03a"},{"version":"1.0.0-beta01","sha1":"a88ee40e1881eb19037cd840a3164ffb52fe96b0"},{"version":"1.0.0-beta02","sha1":"5c6907145c0afbffef0b7d1761f58cfcc5254331"},{"version":"1.0.0-beta03","sha1":"e16df47c4025dcb166f5c9dc828c24545d759dbf"},{"version":"1.0.0-beta04","sha1":"1c79bf126f5740a8f4f3185aabdbabb9d97bf2a0"},{"version":"1.0.0-beta05","sha1":"a9e56ba4ff915335d0fdb62865a46cdda73b1f33"},{"version":"1.0.0-rc01","sha1":"271ad743d0d964f012df820f41cb96f57e9f7ba5"},{"version":"1.0.0-rc02","sha1":"6e8680abc80c2b5ef72a6a7fb760e33cfbc871b1"},{"version":"1.0.0","sha1":"f66b895b5e70eaa9e74dcc3023f900da3559ec44"},{"version":"1.0.1-rc01","sha1":"771571c336b65830a59c718ba3ea83b03d4cf207"},{"version":"1.0.1","sha1":"e2d9905dfe2fccd79d990f2db31ac730e56722e4"}]},{"package":"work-firebase","versions":[{"version":"1.0.0-alpha01","sha1":"57f4ec3bad6ad4b1eae83d9c982e52ac4097e064"},{"version":"1.0.0-alpha02","sha1":"4c51fb64ff99e770a9e5caa733943ec71e688f24"},{"version":"1.0.0-alpha03","sha1":"61f5adaf5e541d9c1c373ec8b6e1979a8f2b5bae"},{"version":"1.0.0-alpha04","sha1":"c8b352d6cb07da89b25078466510dc433e0c0c4a"},{"version":"1.0.0-alpha05","sha1":"4cfacb62610e66ddfe987ff091fc9c2145a6f89e"},{"version":"1.0.0-alpha06","sha1":"e289610ee6959e24478e79fe27b00aa1bf45b62a"},{"version":"1.0.0-alpha07","sha1":"df4620c3e253400d0b6221e1b6e1ed253a8be40c"},{"version":"1.0.0-alpha08","sha1":"8cb06c4812d25ecd85a3402a5b0fb22591de8e95"},{"version":"1.0.0-alpha09","sha1":"d718abf8cc52c0b643a3b20cdc3f808ab67ba543"},{"version":"1.0.0-alpha10","sha1":"6d08fe217062181e97c87bfa991abe2133f83404"},{"version":"1.0.0-alpha11","sha1":"6cb3637a669597762b82c3b66091e9bbc26e9117"}]},{"package":"work-runtime","versions":[{"version":"1.0.0-alpha01","sha1":"416f2bb87962149699ffaaa45b22a31471f39510"},{"version":"1.0.0-alpha02","sha1":"cc516f48a5c26494c2febe9954c2dc8c30b3fd4f"},{"version":"1.0.0-alpha03","sha1":"c70f03634f7f268d0b302b92b80687a5d13aab0a"},{"version":"1.0.0-alpha04","sha1":"c695e712d4e158d846b62a330ec74db90de7d079"},{"version":"1.0.0-alpha05","sha1":"558283954ac18d94a23c3b6180aa94b58e9b93e0"},{"version":"1.0.0-alpha06","sha1":"210715a099f221e87a639986ca4d05e437d30553"},{"version":"1.0.0-alpha07","sha1":"d5ddef91d7e67659a2c538089bc935207ff2d2a7"},{"version":"1.0.0-alpha08","sha1":"f30c4addf5dfedd5bb3579a5151f451be3422cc4"},{"version":"1.0.0-alpha09","sha1":"81530215e15bf46cf9d865c582252f6d82f55124"},{"version":"1.0.0-alpha10","sha1":"7a555a9d0a3f7545327c83131570151cdbf4f2a0"},{"version":"1.0.0-alpha11","sha1":"b4060e4c312d3cbe0e69c456f7e5141d82034291"},{"version":"1.0.0-alpha12","sha1":"5f348e7e25a6ecf862aefd5ffd9dd23c75d8a2c8"},{"version":"1.0.0-alpha13","sha1":"8c2b79d5fa55a559465408bab7892e2a35553bfc"},{"version":"1.0.0-beta01","sha1":"f6696d5a50a646b5d97f3417d5d0aaea26ab4a10"},{"version":"1.0.0-beta02","sha1":"c9e3b602e6f0b7025b594d3a75acdcc530c7a549"},{"version":"1.0.0-beta03","sha1":"7d59e4b7bafa8cecc3b0383163775535b97d26d7"},{"version":"1.0.0-beta04","sha1":"1ce4f002fcae907b8284421c20b0572b1e1c0c8d"},{"version":"1.0.0-beta05","sha1":"1d80f647ee608b0114945379d33306d33d391e18"},{"version":"1.0.0-rc01","sha1":"7d33689011376692338ffd900a42ec2685ce01d5"},{"version":"1.0.0-rc02","sha1":"e7940e53ce5699658e0c3e899fad3c04880d7a8f"},{"version":"1.0.0","sha1":"84de2eca2c6ade5a42a698e7cfee09b6b56adcae"},{"version":"1.0.1-rc01","sha1":"78c31dff661ba5685c583c01f87eb34e28fc6ee0"},{"version":"1.0.1","sha1":"a32f9c42722d87cf7e7f6f2031cc21a1f78376ac"}]},{"package":"work-testing","versions":[{"version":"1.0.0-alpha01","sha1":"e258b56e1d2677eb8f46ee01af3e856fb5fb8a74"},{"version":"1.0.0-alpha02","sha1":"4b767ff69f8feb5c7f959dd6767a57018f618636"},{"version":"1.0.0-alpha03","sha1":"23033e4a922de65238c5d3ff73670e62be39e8d2"},{"version":"1.0.0-alpha04","sha1":"269ff40de75008dafceae12202dedabf017f8c08"},{"version":"1.0.0-alpha05","sha1":"5e42bf00a80c9e918f35b9fdbe88892bcc1d957f"},{"version":"1.0.0-alpha06","sha1":"11453de835fedf41bd9a92a8933ecd940d2dce10"},{"version":"1.0.0-alpha07","sha1":"6d2bc338101ad49071815087a0e42d6123cf85fa"},{"version":"1.0.0-alpha08","sha1":"fcae631355bb460771781b8ccd0f95e746ae6e59"},{"version":"1.0.0-alpha09","sha1":"d1e47fe37cca7b4682b25c7f30075813f42f9780"},{"version":"1.0.0-alpha10","sha1":"e3b4c56ec281bae40be8873bdf3826bee5919ec8"},{"version":"1.0.0-alpha11","sha1":"a54354c9ef6c589bb15153fd60d334ed55ecd4a6"},{"version":"1.0.0-alpha12","sha1":"64304c8822a3ffea731e177cdc93265db703b075"},{"version":"1.0.0-alpha13","sha1":"5cd4232a46a4d400201ce6bb145229a78e3fe634"},{"version":"1.0.0-beta01","sha1":"533ecf883e83917dd3906dfddab8c568c93716a6"},{"version":"1.0.0-beta02","sha1":"a081d072efd2ee9a359e5d9538a67e26066a9d39"},{"version":"1.0.0-beta03","sha1":"64744275727812ba46f2db1ce0b4466dae2ba745"},{"version":"1.0.0-beta04","sha1":"e74115781f23a38715259d2fc1a142324b593322"},{"version":"1.0.0-beta05","sha1":"ba2378cba4a14bbf2133b86952a05cc5329710c0"},{"version":"1.0.0-rc01","sha1":"bd90765d8a57090f153f4535173cb4c5dfe2e2ce"},{"version":"1.0.0-rc02","sha1":"4e99c1f5ff0f212d764c72ebfec89fafe0149ba9"},{"version":"1.0.0","sha1":"16e5aaefb0fd47bf671927e4a656cfb719361141"},{"version":"1.0.1-rc01","sha1":"cdf91613a80a13dc26abe5957de53de378cdcd6e"},{"version":"1.0.1","sha1":"357ec71b2f8d709d19cc8fd349c7c03cf3398ee6"}]},{"package":"work-rxjava2","versions":[{"version":"1.0.0-alpha12","sha1":"8028172909bfbffb0a3f38b4078b0c2d970e92ba"},{"version":"1.0.0-alpha13","sha1":"d8067a56f7719288a37ad915b35869504e2c6c6d"},{"version":"1.0.0-beta01","sha1":"dc22bdd3874b5fbed1c9fb5435a08f781e6fb65d"},{"version":"1.0.0-beta02","sha1":"bae5071c9631659a57149b67f83fa6f2d5d207cb"},{"version":"1.0.0-beta03","sha1":"84c089bb7040176d3543e3b7bce20efd565e2c80"},{"version":"1.0.0-beta04","sha1":"b6e80a08841bd387185a921cbdc1b1871402a00a"},{"version":"1.0.0-beta05","sha1":"3ab56e71f04f29f9ca37155e0b4bffcda981ed34"},{"version":"1.0.0-rc01","sha1":"669cb60250b45a28cb458d93fe87042019bd04eb"},{"version":"1.0.0-rc02","sha1":"44d6f237f9c0017b142c50591be06adabe4b1bc3"},{"version":"1.0.0","sha1":"8055ab9238acaec8150a39012590f563fcc923f6"},{"version":"1.0.1-rc01","sha1":"c4438608d65b5506bd9dc466b67ce98c799eb01e"},{"version":"1.0.1","sha1":"c3dc1866f6ff1b10f02fa2d2457f86b81129524f"}]}]},{"group":"android.arch.navigation","update_time":-1,"packages":[{"package":"navigation-common","versions":[{"version":"1.0.0-alpha01","sha1":"c1e41c9e800fc1021512bfe4de753093a4edee5c"},{"version":"1.0.0-alpha02","sha1":"4bfa33c2aaf3cf85e873ad21ef83fcfda4c08232"},{"version":"1.0.0-alpha03","sha1":"8d36cf6e919aa02742c8217e68850d159514aa29"},{"version":"1.0.0-alpha04","sha1":"69a4dc9522eddd4eb31a6745d4fde1f7050f8fd3"},{"version":"1.0.0-alpha05","sha1":"4b75930dbbe9047a54ed309c2338b24be0312019"},{"version":"1.0.0-alpha06","sha1":"f235cac8b50a0e4625cad5244b6cd6f8d3d994fc"},{"version":"1.0.0-alpha07","sha1":"dcaadbe0188933901b0ce67b1ac52d9b9375986a"},{"version":"1.0.0-alpha08","sha1":"44413c8ab78dfd6fc285ffe459dd9fbd6652cbbc"},{"version":"1.0.0-alpha09","sha1":"d49bc640819a0bfc174076ab35fc76836a3df7ac"},{"version":"1.0.0-alpha10","sha1":"ab41003d4b3b2869a89bb59a3515c9e3356c6b88"},{"version":"1.0.0-alpha11","sha1":"fcca440638e53173b56f5e3e81cfd59fa20925a7"},{"version":"1.0.0-beta01","sha1":"6d5ff888c2a9538eca864b5ca365539381e18c63"},{"version":"1.0.0-beta02","sha1":"2288fa9c437e668fa2e0370feb856d83fef5ca08"},{"version":"1.0.0-rc01","sha1":"95844aae199d1dd2ce71254e74a43346f030fe3e"},{"version":"1.0.0-rc02","sha1":"8267b6dbbd8827706a8341795f3ec8a71fbde999"},{"version":"1.0.0","sha1":"970c136e98e9e331e241e81f84edea39d20633a5"}]},{"package":"navigation-safe-args-gradle-plugin","versions":[{"version":"1.0.0-alpha01","sha1":"791fb4346d8c03765e60138220714bb69082b746"},{"version":"1.0.0-alpha02","sha1":"eabe83280e384f8d1d16c688640b4b9182053b3c"},{"version":"1.0.0-alpha03","sha1":"7f426b097179f2e0567c2477a9d93603e7139362"},{"version":"1.0.0-alpha04","sha1":"2f569f677b6a8ed24ebace9ae39ae9a6bfd73149"},{"version":"1.0.0-alpha05","sha1":"f1911096e7d1a16d4fc73f7a5c4c263936d35f4b"},{"version":"1.0.0-alpha06","sha1":"c37a10be4afb2a0224f606eee1a9868988ba1d28"},{"version":"1.0.0-alpha07","sha1":"17575992b4df53daf4b7ad36cb6e74d7b5e08fa1"},{"version":"1.0.0-alpha08","sha1":"dc683c80d265af78ca292b83816626b0b3718cdc"},{"version":"1.0.0-alpha09","sha1":"9f014c2cdec2b13e67c628bc7584b7d742734da7"},{"version":"1.0.0-alpha10","sha1":"7e02c96e53789f0d697907c40518e7e77ae31f7c"},{"version":"1.0.0-alpha11","sha1":"7e02c96e53789f0d697907c40518e7e77ae31f7c"},{"version":"1.0.0-beta01","sha1":"6232fa40470f2845f383a2a2a52b1f5d7170031c"},{"version":"1.0.0-beta02","sha1":"9b3e6e1af11a375981f2a1d9b034f13568e2cf51"},{"version":"1.0.0-rc01","sha1":"9b3e6e1af11a375981f2a1d9b034f13568e2cf51"},{"version":"1.0.0-rc02","sha1":"9b3e6e1af11a375981f2a1d9b034f13568e2cf51"},{"version":"1.0.0","sha1":"9b3e6e1af11a375981f2a1d9b034f13568e2cf51"}]},{"package":"navigation-runtime","versions":[{"version":"1.0.0-alpha01","sha1":"5c39345ca9c8673ce4ba01e8429730acb3b1b902"},{"version":"1.0.0-alpha02","sha1":"d25c69e9d8805bf327f77adace3a8eff30040fef"},{"version":"1.0.0-alpha03","sha1":"d1bd370d4b0111fb3d07ecdc5c443ded93b4a462"},{"version":"1.0.0-alpha04","sha1":"f2b2faf778527bbc03592e8169cd1f4b56b80d58"},{"version":"1.0.0-alpha05","sha1":"fefe251423d9db18d07f7c566ad6d89c408f603c"},{"version":"1.0.0-alpha06","sha1":"ce347ba47722264e185d2f756aa770fab6d5e85e"},{"version":"1.0.0-alpha07","sha1":"888c5502ebfec95c278958e65fdf6849ab0abaf9"},{"version":"1.0.0-alpha08","sha1":"83d7ba14ede50ecea317ad7e27f96e28e997a667"},{"version":"1.0.0-alpha09","sha1":"41a5c32b9253acb60865439b38afb31ff8f22c1e"},{"version":"1.0.0-alpha10","sha1":"ef077babeb56fc2d9e8543c8fac97e0ad50e809d"},{"version":"1.0.0-alpha11","sha1":"41f46a5a99a49c8fa0ae0d8e636f324a498719b7"},{"version":"1.0.0-beta01","sha1":"9bebce94c491c2cf5c47c160bf6234db65811162"},{"version":"1.0.0-beta02","sha1":"85ca0d5554e10b379877e5397329efd97c0351e9"},{"version":"1.0.0-rc01","sha1":"6ea0ed98ddffdfa9c18c39edd4b74cc4266d3048"},{"version":"1.0.0-rc02","sha1":"436fcb8e3ab3bc72e6822ac32db8848d5efce9f4"},{"version":"1.0.0","sha1":"6fb314e53e40ce02811bb7eececba8f9f3238545"}]},{"package":"navigation-testing-ktx","versions":[{"version":"1.0.0-alpha01","sha1":"fe959379563248bae3ad4d88c1e2d42e1ab6ea4f"},{"version":"1.0.0-alpha02","sha1":"b029d4b1bde4969924a484419695438f08631963"},{"version":"1.0.0-alpha03","sha1":"41a961b30c46deb55bd80bb2d1543f1c5c7bc0b0"},{"version":"1.0.0-alpha04","sha1":"497f1d5d5c45344b360e7d3a1efb4b5b3fd3cec4"},{"version":"1.0.0-alpha05","sha1":"76528fba5ef9ea2073869d29fbdf3c1b91d54e8f"},{"version":"1.0.0-alpha06","sha1":"1dae9a1dda3d5f0373677a13f8ce63a7e95e159b"}]},{"package":"navigation-common-ktx","versions":[{"version":"1.0.0-alpha01","sha1":"a1e15cd29feb9f84bf2c4aa7a9dfe1fef845f60b"},{"version":"1.0.0-alpha02","sha1":"1cbe71913f6a98c07cb73d20c92b3b67239935ae"},{"version":"1.0.0-alpha03","sha1":"6605720cdf6c036ebc254327d1809473e30951bf"},{"version":"1.0.0-alpha04","sha1":"3bbcf27f37afdf5216d0e3f1fa53f9daae27f645"},{"version":"1.0.0-alpha05","sha1":"84594629c246e8fcb8288d48ba501542adf60302"},{"version":"1.0.0-alpha06","sha1":"2b74c2592ad86ab2c50d85cebfdc58a59e7bb1f3"},{"version":"1.0.0-alpha07","sha1":"c3706f5f314e06e9d63f9140a6acc722a7a5760e"},{"version":"1.0.0-alpha08","sha1":"9aad413f047fcfe9755c0fd44fcc6bb301b4528"},{"version":"1.0.0-alpha09","sha1":"d34477ffb6418335d31499e6c99051ef59847a43"},{"version":"1.0.0-alpha10","sha1":"9cbc92c1b216b255d4cc5e76a58e3dfd6217e335"},{"version":"1.0.0-alpha11","sha1":"c67ca341f145d0994f2aa5758fad1dc49151b47c"},{"version":"1.0.0-beta01","sha1":"23cb5ae2cc4a3586763a84e1fd7c412cd23f162e"},{"version":"1.0.0-beta02","sha1":"b7c26aa3f5b7fcb963b142c21d3597977831859d"},{"version":"1.0.0-rc01","sha1":"34d53c8dd953c94a414ba60b2ae69e2a7c736ceb"},{"version":"1.0.0-rc02","sha1":"b8f9ebc86c94a2a0faa15b53adb2cc4aac6264cc"},{"version":"1.0.0","sha1":"7fe8e8032cc9607f13c0b2b97f69ee541b7d77a5"}]},{"package":"navigation-ui","versions":[{"version":"1.0.0-alpha01","sha1":"32b069f712e27c209fbba45c15fd5bce969c33f9"},{"version":"1.0.0-alpha02","sha1":"ada1f330020461e98ced1a84f3c4f9a7bd3c3586"},{"version":"1.0.0-alpha03","sha1":"e820ba187bb8a24088db32c2360ff633a238b1ad"},{"version":"1.0.0-alpha04","sha1":"f7afb9bcb18efa6350c1d3bfa05c151e25b5747c"},{"version":"1.0.0-alpha05","sha1":"20a9123c017fa38ab43d5148bc039f6ed5e30ad5"},{"version":"1.0.0-alpha06","sha1":"7a4b56dbf5470866a80eaf43178bba0d9ec4e52b"},{"version":"1.0.0-alpha07","sha1":"3aa9c83477c26da881c003e1cfe22f84ba6f47fe"},{"version":"1.0.0-alpha08","sha1":"f2b95a962e69f8f967363511aea50c3d5599cde6"},{"version":"1.0.0-alpha09","sha1":"a5b66e55b86125bffe863b6cfa705a1755f2d1cd"},{"version":"1.0.0-alpha10","sha1":"3e949e7e18b2584ce55ba7e079719ebb64c33ab2"},{"version":"1.0.0-alpha11","sha1":"e105950cd4f235f17fa7f236771e164e6b8bb096"},{"version":"1.0.0-beta01","sha1":"5b2fdeced9f7bad5eb2813571a489bec277ac28f"},{"version":"1.0.0-beta02","sha1":"a1232f16380bf9a4f2c08cbc239f5790bfdb8a6c"},{"version":"1.0.0-rc01","sha1":"ef41eb6bf0552eafc9740833a1ddda6a14513eff"},{"version":"1.0.0-rc02","sha1":"22f6e3282a61e37aea78b58f595e3e1cae7fa4f9"},{"version":"1.0.0","sha1":"329d3a63abeaebb711cb968a6f1b727dc5de0498"}]},{"package":"navigation-fragment-ktx","versions":[{"version":"1.0.0-alpha01","sha1":"bfbc628533d2332c80fb4d572b03f4a1f126194b"},{"version":"1.0.0-alpha02","sha1":"68e486c2701b6cd5d3c7ff9f36b97eba365aca5d"},{"version":"1.0.0-alpha03","sha1":"f3662e61e0e4875b62191651586c14db6e228873"},{"version":"1.0.0-alpha04","sha1":"3837053d23af68fa2416d399a89a9421d28ea4eb"},{"version":"1.0.0-alpha05","sha1":"81fab4b9c9aad5e1c3732937cfd6c29ba7d0c26c"},{"version":"1.0.0-alpha06","sha1":"2daed038571dc1eebccdc404aa1e9cfe4a9845df"},{"version":"1.0.0-alpha07","sha1":"931abe72201da86e54821f098c21f1b6e1e213f5"},{"version":"1.0.0-alpha08","sha1":"ddf3d61d2924ba5db305aefd6a3ea1173b81d793"},{"version":"1.0.0-alpha09","sha1":"3a58e9bc950e198589d816438e30489f457d0469"},{"version":"1.0.0-alpha10","sha1":"f7d332e44b225db36cdebbfdf30dcfcc7c5cff99"},{"version":"1.0.0-alpha11","sha1":"6698f4f77bcd21d1ede10ed23945f205071e5b02"},{"version":"1.0.0-beta01","sha1":"1421981e9d460001b399f632c3561106fbd2d401"},{"version":"1.0.0-beta02","sha1":"b67bd13f36189271aadd4a0b881defa925d87bf6"},{"version":"1.0.0-rc01","sha1":"315ee4bf08ed072b2ce147816cd523b1752bd79a"},{"version":"1.0.0-rc02","sha1":"52725a3991639b7c47b52391ff887dfdd8feee3a"},{"version":"1.0.0","sha1":"39e3d90a903abd085627b8d1e56d3780894a59ac"}]},{"package":"navigation-testing","versions":[{"version":"1.0.0-alpha01","sha1":"a054dfef3f585a1c35e966824eb6b9ae2edac746"},{"version":"1.0.0-alpha02","sha1":"dd5aa5789edf365b93fa7937010b89334c7ec729"},{"version":"1.0.0-alpha03","sha1":"a550ee226b7b21c2203ec54d6da8f64adb20e660"},{"version":"1.0.0-alpha04","sha1":"23b59898eedd61834f888dcc6a4a866f0a97f04e"},{"version":"1.0.0-alpha05","sha1":"69164e019a6f21203755e3957c5aa03494b7e371"},{"version":"1.0.0-alpha06","sha1":"a980287def2953ae788227e21b32c8de268e5709"},{"version":"1.0.0-alpha07","sha1":"8727f692f15dfaab1dfec2a81a19448ee0602021"},{"version":"1.0.0-alpha08","sha1":"a18743676cce2c9065c048a91ae68340c2420cb8"}]},{"package":"navigation-ui-ktx","versions":[{"version":"1.0.0-alpha01","sha1":"a2eb368a8f8fd01e70ad2b351fe840a508d32748"},{"version":"1.0.0-alpha02","sha1":"c1bd622fa0ecbcdd4c6971c10a09e7c7089ef3be"},{"version":"1.0.0-alpha03","sha1":"9da968bf54957063f83251390aa6f06a0937783"},{"version":"1.0.0-alpha04","sha1":"f063275b758ddda7337594674c600468a87005e6"},{"version":"1.0.0-alpha05","sha1":"bfc19f59859826914615c5ecd8aeca8edd8544ba"},{"version":"1.0.0-alpha06","sha1":"2b29d824bf193ffefc80c8f4175b5c2a93175702"},{"version":"1.0.0-alpha07","sha1":"73d803a6ce27d7134188f53de806e11925e6c3c8"},{"version":"1.0.0-alpha08","sha1":"7800f4576c48d8c50a768fdfec1333812e6c2216"},{"version":"1.0.0-alpha09","sha1":"d532d28bd3b541f33f9283c184e0178dd39b32f"},{"version":"1.0.0-alpha10","sha1":"6a4d4da9605b5831df127f80a7b0b9029036cf04"},{"version":"1.0.0-alpha11","sha1":"69fc4903e303857b3deb31b338379bfb1e913ce6"},{"version":"1.0.0-beta01","sha1":"c6c8b41063b532fb5bcda1f9b90694b1e394ef19"},{"version":"1.0.0-beta02","sha1":"1dd7a25168cc4042b2727f27bb82b78281a76741"},{"version":"1.0.0-rc01","sha1":"24c8d188182cd99eb8007ecaa432d96b25ae4692"},{"version":"1.0.0-rc02","sha1":"f81aae40a461dde15f071b5f8bd5f53fc86eddbe"},{"version":"1.0.0","sha1":"9f70deae612b355dc65d8c5855717ebc9ec11e31"}]},{"package":"navigation-safe-args-generator","versions":[{"version":"1.0.0-alpha01","sha1":"df8cf8899a4ca4373a47adc5013d322b3b217b0"},{"version":"1.0.0-alpha02","sha1":"e39882bb0311b5b2cf4dfb3a74e96090a1829dfd"},{"version":"1.0.0-alpha03","sha1":"293d6f57aa3a9adc5f613f52f2e90e7b9b946dcf"},{"version":"1.0.0-alpha04","sha1":"c476d086c56390d03ad5394025a7f9f8dddbb95c"},{"version":"1.0.0-alpha05","sha1":"a9251959fa8c1056e393231f94c26ed03621b1a3"},{"version":"1.0.0-alpha06","sha1":"cb1933bcdc49b4f605cc5d04fa713c8c4b38ac16"},{"version":"1.0.0-alpha07","sha1":"5f1dfeb8c7b90e3d55acb4feb9a2b665234f058c"},{"version":"1.0.0-alpha08","sha1":"dc157bb03f39fca0ea61559d5f716bb22e7ca944"},{"version":"1.0.0-alpha09","sha1":"f183138c2b2cbdf0c9c30fdd2fd2676b0a415872"},{"version":"1.0.0-alpha10","sha1":"d5799a3d5e0abde41945be98b3031c4e84b59d89"},{"version":"1.0.0-alpha11","sha1":"74ab09b5a599d8ce200a250cfb4c7d91cd7e2577"},{"version":"1.0.0-beta01","sha1":"e3bec91616d26d6f4e56796b4c5048472065d760"},{"version":"1.0.0-beta02","sha1":"3b7d8650ab83e8db4ec4fc32810e116ecae85986"},{"version":"1.0.0-rc01","sha1":"3b7d8650ab83e8db4ec4fc32810e116ecae85986"},{"version":"1.0.0-rc02","sha1":"2c7a571ecf4009c5dc58c3499cabf9ee3fa16777"},{"version":"1.0.0","sha1":"2c7a571ecf4009c5dc58c3499cabf9ee3fa16777"}]},{"package":"navigation-runtime-ktx","versions":[{"version":"1.0.0-alpha01","sha1":"9c6dab92308b38ce36ae568864b0ba378da8ad3b"},{"version":"1.0.0-alpha02","sha1":"593d421284cab1c2110c5a3fdfef9b6888680b17"},{"version":"1.0.0-alpha03","sha1":"4483c534a7f98a07bc1605a19e308d8c61bf39a0"},{"version":"1.0.0-alpha04","sha1":"72a2a146e8362f9be68caae60d3924dd4e16010c"},{"version":"1.0.0-alpha05","sha1":"7efdd9f188b185c62558202a56079facb2048f58"},{"version":"1.0.0-alpha06","sha1":"d0fb389f2c602d0b8275bd3bdc78a1f489aeaf78"},{"version":"1.0.0-alpha07","sha1":"669f530c53f735e33d82a6ff1e54c84bd5957ea0"},{"version":"1.0.0-alpha08","sha1":"89be820d54f14b55608f974ead0dffb351c743ff"},{"version":"1.0.0-alpha09","sha1":"39793408ec1a02277065b490f8f0e7ad228544b2"},{"version":"1.0.0-alpha10","sha1":"cc097352a7be37f8df83541237dc0b33b61b620f"},{"version":"1.0.0-alpha11","sha1":"988a1694eda80b88073b8475a6a8cbfe9601c9e1"},{"version":"1.0.0-beta01","sha1":"a518b4bf06282094b96ade40bd43090edcbd451e"},{"version":"1.0.0-beta02","sha1":"6dd8817f0c35af5e92fb1db70470667a5459418e"},{"version":"1.0.0-rc01","sha1":"b49d165117c1714ba1a1750ce48a56b09caea1d4"},{"version":"1.0.0-rc02","sha1":"609afd4b05df141504dcc1a1ecd243d52dd52c60"},{"version":"1.0.0","sha1":"a12cf85cf87e3adedf233a9d842acf1baf5516e8"}]},{"package":"navigation-fragment","versions":[{"version":"1.0.0-alpha01","sha1":"2738615cf26a92e7e7f885a243be7977d628c58b"},{"version":"1.0.0-alpha02","sha1":"54abbcdd0beb729eaa9354612eb1af6ef88966fe"},{"version":"1.0.0-alpha03","sha1":"898d9bb32e759a8412e2744a9e4f8e9eb2e15790"},{"version":"1.0.0-alpha04","sha1":"7ee5f527721e86efdf7410247b293d6df044c92f"},{"version":"1.0.0-alpha05","sha1":"fb765636727b74656831a3515dd9d5805e5d5c4f"},{"version":"1.0.0-alpha06","sha1":"a0711160949a771a461598782decf0c86ee514ae"},{"version":"1.0.0-alpha07","sha1":"c7ff29226ff7970376dc01817208b60cefd456db"},{"version":"1.0.0-alpha08","sha1":"558623207df477402b9e4bcff95400252da8b599"},{"version":"1.0.0-alpha09","sha1":"148c52a2575d97d3787ce665f1e199cb31baef88"},{"version":"1.0.0-alpha10","sha1":"98d251d267b2c0f203418e5c4a45fd06d12adcff"},{"version":"1.0.0-alpha11","sha1":"2fdffd05788dafbbf9f5fd1c5c54fd949c4dbe3e"},{"version":"1.0.0-beta01","sha1":"6a1f7fc24e37ee254421b9e8f042adbfe3e3677c"},{"version":"1.0.0-beta02","sha1":"37a7ae64ad2f0e4fbc44f1aff3b81ffc0675989b"},{"version":"1.0.0-rc01","sha1":"923c57a3268d98c752d9fdebba04b0e10d8eb963"},{"version":"1.0.0-rc02","sha1":"2f03cb7cfe5fe7d1961fc317d1e53d58b4d58a0"},{"version":"1.0.0","sha1":"4251e43f7630d5bc839f5d31522f46c47a1974c1"}]}]},{"group":"androidx.mediarouter","update_time":-1,"packages":[{"package":"mediarouter","versions":[{"version":"1.0.0-alpha1","sha1":"a460c3c71015222fb434c2b4e28f6fd0b12b27ce"},{"version":"1.0.0-alpha3","sha1":"b0f2446ec4a21bc66134d6086bd220d8d9bd0a1a"},{"version":"1.0.0-alpha4","sha1":"d650a6740753805adc263df24a313a5f308588d7"},{"version":"1.0.0-alpha5","sha1":"9773d669c9873fad132a77b532a599da3359e68d"},{"version":"1.0.0-beta01","sha1":"efcc0826113742647c6081f87ad8174cdfc41900"},{"version":"1.0.0","sha1":"9ac6729cd7f1cce3d92b1caec4fbbaebc141a9fe"},{"version":"1.1.0-alpha01","sha1":"8f581ee57abd386f597a6622d4704c9694cea679"},{"version":"1.1.0-alpha02","sha1":"c8c683679fc19cff7e951a6aa669f04e2bb3e145"},{"version":"1.1.0-alpha03","sha1":"bddaac9670f4b6890779394501272028280c0bec"},{"version":"1.1.0-beta01","sha1":"298951923f73502cabe6d2c3fa07ea21842d2473"},{"version":"1.1.0-beta02","sha1":"7bee6099ca27920d6177b8139178330b54ac10d7"},{"version":"1.1.0-rc01","sha1":"740df5e8408f549f15d801d15cb5c65e42c8589c"}]}]},{"group":"androidx.percentlayout","update_time":-1,"packages":[{"package":"percentlayout","versions":[{"version":"1.0.0-alpha1","sha1":"29ea80b00da0f89c18db8faf9e24850749790b72"},{"version":"1.0.0-alpha3","sha1":"48b72738568ac2bb521356ae07e87cc45d0755de"},{"version":"1.0.0-beta01","sha1":"c538e5eeb6247d5dd0cea31418158255ba75386c"},{"version":"1.0.0-rc01","sha1":"414d5f589f8157be90d5be36b2c20d1b29220174"},{"version":"1.0.0-rc02","sha1":"4a4ae837f70e79f6f75e9b6d55ffad777484c68c"},{"version":"1.0.0","sha1":"3a21131205635acbc94ace74d93af196eab76bac"}]}]},{"group":"androidx.emoji","update_time":-1,"packages":[{"package":"emoji","versions":[{"version":"1.0.0-alpha1","sha1":"6a6b20d103e8a5b1bf79d85f0d7a6b6db94c17ba"},{"version":"1.0.0-alpha3","sha1":"471fded7e4946f0764124ed8deb1f81a2d6b7b2f"},{"version":"1.0.0-beta01","sha1":"e7453e8f5bf10121b1f9ac7f4a7295e7f5d68116"},{"version":"1.0.0-rc01","sha1":"852c17b93b5d31274ff7e9444a1227c7a801e097"},{"version":"1.0.0-rc02","sha1":"4ddf3c81f678c1b188d2aeb2ecdb9149acf124bf"},{"version":"1.0.0","sha1":"9656ed5c30709e489e6c61a5a230de87bdc0c9f5"}]},{"package":"emoji-appcompat","versions":[{"version":"1.0.0-alpha1","sha1":"a888fa56cb0d6fd44bdd982ec778d4cc31efde93"},{"version":"1.0.0-alpha3","sha1":"43ed99846eb7315184af7f3a18f81703394db9cf"},{"version":"1.0.0-beta01","sha1":"b858e098dee79aae71f86e47fff8eef30cbbc3c2"},{"version":"1.0.0-rc01","sha1":"59fabed21b78c1ebc9f143ec848f46bfadd47a1b"},{"version":"1.0.0-rc02","sha1":"9ba6c85002fcf61a39731275ec74cb1ed56271ba"},{"version":"1.0.0","sha1":"7927f5f6abad1f0c66979d252be0be3fa545694b"}]},{"package":"emoji-bundled","versions":[{"version":"1.0.0-alpha1","sha1":"49f53bb573f5f399dc455f8adae45d93d62643e2"},{"version":"1.0.0-alpha3","sha1":"2618384d872cc56be2e8147800ec571acafedf64"},{"version":"1.0.0-beta01","sha1":"580325e58d0c06e47fe362fdd9b52da70891f7c0"},{"version":"1.0.0-rc01","sha1":"d15eb1015dcf63b039132e9947dbb5c9f5fe36e2"},{"version":"1.0.0-rc02","sha1":"65045528ae57f55b593ae48ba078cb234cd58ad6"},{"version":"1.0.0","sha1":"6bef678e8105299fdfecd43ee9f1465f398fdd50"}]}]},{"group":"androidx.cardview","update_time":-1,"packages":[{"package":"cardview","versions":[{"version":"1.0.0-alpha1","sha1":"fe7909677c6ab80d6fc477ea07508cf424411249"},{"version":"1.0.0-alpha3","sha1":"f97ffda031e35f3d4e5485b89b1a46d4a7ae1374"},{"version":"1.0.0-beta01","sha1":"75b47f5346ea087a8a49b72e610f2ca9ec3afa2f"},{"version":"1.0.0-rc01","sha1":"53970fa2eeb483e5c2968183bdc06b2e084213e6"},{"version":"1.0.0-rc02","sha1":"37435bfb1136f42f07d269c1094b6b3cb9180bd4"},{"version":"1.0.0","sha1":"158dbc2e2bc502815821191b04446b8f663c1874"}]}]},{"group":"androidx.preference","update_time":-1,"packages":[{"package":"preference","versions":[{"version":"1.0.0-alpha1","sha1":"775269e5b7b4f3c27bd0a08dc0d73429c8e83134"},{"version":"1.0.0-alpha3","sha1":"3643fa1ca5b738742aa395e227ae161911364310"},{"version":"1.0.0-beta01","sha1":"a123d182797fb214cdae86512c6b7debfa6a8577"},{"version":"1.0.0-rc01","sha1":"789c7255bec3c3cd36e717c2533d50d10e29f284"},{"version":"1.0.0-rc02","sha1":"94a7f1d1ef4a5a74f463e23f889e3737796e86d0"},{"version":"1.0.0","sha1":"3117bbd5560dba2eb1253c2a48a8a5a103db0a22"},{"version":"1.1.0-alpha01","sha1":"16a1d399bddab9d69d97072c1656b3c3ec9d13c"},{"version":"1.1.0-alpha02","sha1":"356e006f44f59bd91955a6fc78d9ad260adb992a"},{"version":"1.1.0-alpha03","sha1":"b0e056f15843eab97f862ebe2cb53f50a724700a"},{"version":"1.1.0-alpha04","sha1":"2e8fbecf8975a1e50c4e6357a9f5407fb13aca72"},{"version":"1.1.0-alpha05","sha1":"7578da1956feceb07aba5119df58f2cf722d2745"},{"version":"1.1.0-beta01","sha1":"bacd6a9a546a8cfcb3fcf2924602e1797e9c91c6"},{"version":"1.1.0-rc01","sha1":"5c27f59316bf23d1503aa1b73b3389c7c7ffb421"}]},{"package":"preference-ktx","versions":[{"version":"1.0.0-alpha3","sha1":"47e2f99e7017f0b833fa2e00633c77dda8659b9e"},{"version":"1.0.0-beta01","sha1":"66ac9da1018f0e6442ab9a3663f4d51832a75426"},{"version":"1.0.0-rc01","sha1":"615cf11f147474cbbe187ae466d4b0b98cf7e1a5"},{"version":"1.0.0-rc02","sha1":"6521023afd250c02ade2fd339912370cbf8e1f02"},{"version":"1.0.0","sha1":"ecb660620883ab9b3e1300fdf6c8b760e818cbf3"},{"version":"1.1.0-alpha02","sha1":"8af8496d724c3f491075054c0819ad7874a85527"},{"version":"1.1.0-alpha03","sha1":"f66fc6cb4732b9063711ae1b3841942b9fd67a0a"},{"version":"1.1.0-alpha04","sha1":"d2db792af8bcb2fd084d13142c283ef77beff434"},{"version":"1.1.0-alpha05","sha1":"2c038c740f1eb526fb8b765cc7b4570dac373e89"},{"version":"1.1.0-beta01","sha1":"6f959b6524542e18ff1e0083e689f16235a55f9d"},{"version":"1.1.0-rc01","sha1":"87820fb95832d636ad8ee0feaedb7835218b917f"}]}]},{"group":"androidx.wear","update_time":-1,"packages":[{"package":"wear","versions":[{"version":"1.0.0-alpha1","sha1":"8d47cb9ef867c1f9f28bc6dca695efa121f43942"},{"version":"1.0.0-alpha3","sha1":"504c4fe7c144252276d012e77813ae9f14f275e4"},{"version":"1.0.0-beta01","sha1":"b99d4e80c2442f35b678fafdf7369d6070bd38ee"},{"version":"1.0.0-rc01","sha1":"25f1324cb07ceade5765492b44daec081e95cac1"},{"version":"1.0.0-rc02","sha1":"b4a2f99420e4f2fabbfc489594e7c1e6251e475f"},{"version":"1.0.0","sha1":"d45c5df6054f7cd65af92f3535145c30356c6bf8"}]}]},{"group":"androidx.legacy","update_time":-1,"packages":[{"package":"legacy-support-v13","versions":[{"version":"1.0.0-alpha1","sha1":"67ae9e867c6d476fe08c2c1f14933c752bb5d4a6"},{"version":"1.0.0-alpha3","sha1":"66c8159dee256906bc480e1fc5bc15e6f25a698d"},{"version":"1.0.0-beta01","sha1":"b0ee906635b8c7e4000d204bb268ffb5b9b39d3d"},{"version":"1.0.0-rc01","sha1":"b2a978552603df09a85da7044b2341c0811d7d7a"},{"version":"1.0.0-rc02","sha1":"89e9525204a9cfa855490f363b38deb997fb879b"},{"version":"1.0.0","sha1":"3c1add449d79dd8b82bfe2413cef7597b75dfe3c"}]},{"package":"legacy-preference-v14","versions":[{"version":"1.0.0-alpha1","sha1":"85aa12d9dd046937a14c05583887000dd32600d5"},{"version":"1.0.0-alpha3","sha1":"2e9de6932a9826cdcd6f32586c3490f3ff2fbefe"},{"version":"1.0.0-beta01","sha1":"c6a2f36a1ff9b7abfc924469d726e13be8585987"},{"version":"1.0.0-rc01","sha1":"a12346b1e1037f22517c7a30212bdffbefc4d6cb"},{"version":"1.0.0-rc02","sha1":"e0e944aabe2760ab176850e2d622e0b0e03331d1"},{"version":"1.0.0","sha1":"69f57ab940feb39a4047ae141570792659b63be0"}]},{"package":"legacy-support-v4","versions":[{"version":"1.0.0-alpha1","sha1":"84b7ed5635eb1a45134e89c989a7bb1727a9c061"},{"version":"1.0.0-alpha3","sha1":"53479c452fd96208400de4c74f39062cf978c4d8"},{"version":"1.0.0-beta01","sha1":"af0ecaad1950663623f649cca601d4e781a369d9"},{"version":"1.0.0-rc01","sha1":"46c8f47faab425a71c4445f9ddb12e6d351f420e"},{"version":"1.0.0-rc02","sha1":"d9298cfc29b3e95e89e4455e1d594b7dd19a3982"},{"version":"1.0.0","sha1":"3e1271b351e1209661b9fd769cd6681a31678fe"}]},{"package":"legacy-support-core-ui","versions":[{"version":"1.0.0-alpha1","sha1":"72c74705150aacc13fe8cd9849cfd41a9dfe3783"},{"version":"1.0.0-alpha3","sha1":"8007868e6982fc80353cddc13f30974f6f53acd7"},{"version":"1.0.0-beta01","sha1":"d85bf204fc8f82ec27b43992ce4bdec5cfc474a0"},{"version":"1.0.0-rc01","sha1":"da11a06580fc7d5b1e75ebdbbc1275729dfc4cd5"},{"version":"1.0.0-rc02","sha1":"c57386416b6ec5f6b7f248fcc2b6bfd0618e4683"},{"version":"1.0.0","sha1":"61a264f996046e059f889914050fae1e75d3b702"}]},{"package":"legacy-support-core-utils","versions":[{"version":"1.0.0-alpha1","sha1":"cf7932536455a1ddba41504c64978df738fd5fd6"},{"version":"1.0.0-alpha3","sha1":"225915151bf1d3bfee18f7a3a88f10c199f415d5"},{"version":"1.0.0-beta01","sha1":"4d6232ae359f2b53e7f84f49c4a1de88208d8dd0"},{"version":"1.0.0-rc01","sha1":"c636b3ae4ad5db23f8e403138d05a97d723bd86b"},{"version":"1.0.0-rc02","sha1":"ab17657b45e7fffe32026292081f84f9b1a3dbd8"},{"version":"1.0.0","sha1":"9b9570042115da8629519090dfeb71df75da59fc"}]}]},{"group":"androidx.documentfile","update_time":-1,"packages":[{"package":"documentfile","versions":[{"version":"1.0.0-alpha1","sha1":"7a1026daeb59a5522c98847d71ae2d0aca3d2e01"},{"version":"1.0.0-alpha3","sha1":"1d051203e9ed550a5768971d144620b873e005d9"},{"version":"1.0.0-beta01","sha1":"d1c4921ae707f253670ed068c9ac7803d4f6532e"},{"version":"1.0.0-rc01","sha1":"60fb0576dc6e6cd8e7aa70bd8d655407e0fda083"},{"version":"1.0.0-rc02","sha1":"f4b57acbcb73b58863eff479cc18d4f2ec5a2bd2"},{"version":"1.0.0","sha1":"66104345c90cd8c2fd5ad2d3aad692b280e10c32"},{"version":"1.0.1","sha1":"e824bb6e7fc36ec03857da400aa31e136c73cc68"}]}]},{"group":"androidx.car","update_time":-1,"packages":[{"package":"car","versions":[{"version":"1.0.0-alpha1","sha1":"7b351de131a7f0394b55e1fff16f8bae8049ddca"},{"version":"1.0.0-alpha3","sha1":"366163aa40422ba7ad0457994c0721a7291735e0"},{"version":"1.0.0-alpha4","sha1":"e5e858940c1d676cdaccb477cb9184d9b72aa338"},{"version":"1.0.0-alpha5","sha1":"8a6db4bcef9cec597c27a10bfebdcd35766ebfb4"},{"version":"1.0.0-alpha7","sha1":"6f89a50070b57400b2360f5effb3b5f30e7b8be3"}]},{"package":"car-cluster","versions":[{"version":"1.0.0-alpha5","sha1":"7507cb678f90c3f87a6b0309c2796d7cd6c02b90"}]}]},{"group":"androidx.swiperefreshlayout","update_time":-1,"packages":[{"package":"swiperefreshlayout","versions":[{"version":"1.0.0-alpha1","sha1":"605043bc909821146aabbd6134791351b59c5407"},{"version":"1.0.0-alpha3","sha1":"986274b5803d005c019a9be6022c9b212caabb6e"},{"version":"1.0.0-beta01","sha1":"fef63e3061e5d4021009be41553e9fb673273787"},{"version":"1.0.0-rc01","sha1":"3510131397cf338496f1135b5605172b3f38ef26"},{"version":"1.0.0-rc02","sha1":"e4d696d6c118d18f42ae9ba6800dd984cafefbdf"},{"version":"1.0.0","sha1":"4fd265b80a2b0fbeb062ab2bc4b1487521507762"},{"version":"1.1.0-alpha01","sha1":"4f6f9550f3166d0400758c6799c24b8c9b1d082"},{"version":"1.1.0-alpha02","sha1":"5cb9e619bcf4b8d4c0fae0d8530abcaeee292110"}]}]},{"group":"androidx.leanback","update_time":-1,"packages":[{"package":"leanback","versions":[{"version":"1.0.0-alpha1","sha1":"55370180c944f9e8987c3372fb335988e4ca0763"},{"version":"1.0.0-alpha3","sha1":"ee5c4c1989beec797afef110ce5fa3947172b777"},{"version":"1.0.0-beta01","sha1":"e682ea0f1b850ef65c0020ff78da778a7daa69af"},{"version":"1.0.0-rc01","sha1":"66cd21a9aa9e1328b075edffa3d9a51216af4531"},{"version":"1.0.0-rc02","sha1":"92e4f60613586d7fc035b757000c68b106ba69ed"},{"version":"1.0.0","sha1":"65025d699df3d9f98e830de9c16fb29cdf348759"},{"version":"1.1.0-alpha01","sha1":"ff59c3b5e7eae71daf9f1540d3968d708092b27"},{"version":"1.1.0-alpha02","sha1":"55baa0c8b8a39b1cae24460f9b71020fe13577c4"}]},{"package":"leanback-preference","versions":[{"version":"1.0.0-alpha1","sha1":"c70ae227d21af143a463d20c4ad0a0191ca85f6e"},{"version":"1.0.0-alpha3","sha1":"83c6a87bb5ff7d417d771bc89f88554fc5604b99"},{"version":"1.0.0-beta01","sha1":"8b7c11d985b2865eb28df08891dd331988e999f"},{"version":"1.0.0-rc01","sha1":"701c1e5e2f91698be0acbbd32a5b9a15ce729147"},{"version":"1.0.0-rc02","sha1":"e1f2992b95fd115b144d02974d51239960e25d89"},{"version":"1.0.0","sha1":"7954e8d362689ad306321eb667a370786d7b5077"},{"version":"1.1.0-alpha01","sha1":"bbfe26894d1d614649348d2eeaa198ff8a60c2cc"},{"version":"1.1.0-alpha02","sha1":"46700f5f49fe4c718e1d2b1aa53bd6aee90f9f79"}]}]},{"group":"androidx.appcompat","update_time":-1,"packages":[{"package":"appcompat","versions":[{"version":"1.0.0-alpha1","sha1":"66967236c4e843a8829d4e1b7f61e9c73fa3ba0a"},{"version":"1.0.0-alpha3","sha1":"d5226ea53153f05190801f89f61064f953a078ed"},{"version":"1.0.0-beta01","sha1":"a60cca21a1086cbb4dc12d2757f59af3d8143c3d"},{"version":"1.0.0-rc01","sha1":"1991d918634ece8ec00326e3822246aaf3672274"},{"version":"1.0.0-rc02","sha1":"a8c489481073da8b77b87b1c683dc0230cad6868"},{"version":"1.0.0","sha1":"155b5a7193b5b87c3ece2ec444c85cd8de2347dd"},{"version":"1.0.1","sha1":"291b4d72ec881c450287195557d94ad10f1e796f"},{"version":"1.0.2","sha1":"2533a36c928bb27a3cc6843a25f83754b3c3ae"},{"version":"1.1.0-alpha01","sha1":"75b45e0719f55cd3dcbb70cc9e4a46ba03bbe972"},{"version":"1.1.0-alpha02","sha1":"57c1b9ebd681da9d974e8b30c77ce155e87646a7"},{"version":"1.1.0-alpha03","sha1":"7b11689c7cdb3024d9a1d558fb65eb9472c57762"},{"version":"1.1.0-alpha04","sha1":"6765991105d9fdbe88e8a22b3f8acb86b236134f"},{"version":"1.1.0-alpha05","sha1":"1b10560de26f0b73e10a663b1ec2dcc90210bb1c"},{"version":"1.1.0-beta01","sha1":"828a27a5082e94f85e32b2ef9e68d288df8bd588"},{"version":"1.1.0-rc01","sha1":"62955e0a3c6f4566648f30da6e40e44b01221422"}]},{"package":"appcompat-resources","versions":[{"version":"1.1.0-alpha03","sha1":"f4b5d51d41243f4bbb469d952c2ad4cca90ac4eb"},{"version":"1.1.0-alpha04","sha1":"887815a6dcb6c9fa5232beef60fd9155d7614638"},{"version":"1.1.0-alpha05","sha1":"c4f8145a0e3db811474d00c7ffdeec40bfba6092"},{"version":"1.1.0-beta01","sha1":"bea94f8a0abddd246edaddd05ef7eb1b66c8f118"},{"version":"1.1.0-rc01","sha1":"1c8796ad736cbcb539e89630931ca886b2e75212"}]}]},{"group":"androidx.customview","update_time":-1,"packages":[{"package":"customview","versions":[{"version":"1.0.0-alpha1","sha1":"6011c4b2e210fb99c674c13a542edaabf187f9fe"},{"version":"1.0.0-alpha3","sha1":"71e848574d4dacbbd51e6f7183df7961ca58a59d"},{"version":"1.0.0-beta01","sha1":"2168a78263cc950e0d5a37dfe8305f62fefc6c92"},{"version":"1.0.0-rc01","sha1":"db07b3837ba96878b4f77429b417beac598b975f"},{"version":"1.0.0-rc02","sha1":"d1d0ee6457ddd0d958bcf3cf036a78ad019b5986"},{"version":"1.0.0","sha1":"30f5ff6075d112f8076e733b24410e68159735b6"},{"version":"1.1.0-alpha01","sha1":"8f07223c4bc2fe20c68d78624c3f6b68dbd5a263"}]}]},{"group":"androidx.gridlayout","update_time":-1,"packages":[{"package":"gridlayout","versions":[{"version":"1.0.0-alpha1","sha1":"ebbb158ec8af0f69838f6089d6e4ebd0c0bbf1f8"},{"version":"1.0.0-alpha3","sha1":"aae9da46d54cc989b19ba22e505e41cf37e21c32"},{"version":"1.0.0-beta01","sha1":"13cc1f9eabc3b8af23e75e8daa94871d4d19eb39"},{"version":"1.0.0-rc01","sha1":"7542ee63064b0d812ae96030171811a4836214ce"},{"version":"1.0.0-rc02","sha1":"40d13445bc982252afa593db2d39b5439a01acce"},{"version":"1.0.0","sha1":"41f758c6f548bc4199d567dd22e2103c25f3b9a0"}]}]},{"group":"androidx.vectordrawable","update_time":-1,"packages":[{"package":"vectordrawable-animated","versions":[{"version":"1.0.0-alpha1","sha1":"5c4be3e69e281486b620ee289f104be690e1ef82"},{"version":"1.0.0-alpha3","sha1":"4ac83133b264befbc19674a14391b040dc1175e1"},{"version":"1.0.0-beta01","sha1":"bbd7e5633c9433fe23546a1aca5846aa5e35f4a3"},{"version":"1.0.0-rc01","sha1":"5904da0a884d77f1e33c164361161afb627f2b2e"},{"version":"1.0.0-rc02","sha1":"7c48d0f356e4fea7b3e2caf01ae7534a3eeb4fa2"},{"version":"1.0.0","sha1":"a41681ac4e1747f87237e489699089ad46b7a5e"},{"version":"1.1.0-alpha01","sha1":"aaf1e13918de3f8d7590526f9af101863815d833"},{"version":"1.1.0-beta01","sha1":"5b7bba4313a2767f3f88746b0b4f9f7cd02b4436"},{"version":"1.1.0-beta02","sha1":"d651f8f63e286b27dfad13d16f654bc3ddef5845"},{"version":"1.1.0-rc01","sha1":"d574d647c1305a96791edaf097f04fe4fc6db5f5"}]},{"package":"vectordrawable","versions":[{"version":"1.0.0-alpha1","sha1":"5c9d22c52badf4da4cc773bfb0cdd0b26436e1aa"},{"version":"1.0.0-alpha3","sha1":"b47acb9ffadc7871d74858f725e088e9ed92fd1e"},{"version":"1.0.0-beta01","sha1":"9cb564b37f928d0f2188280aacd94b5aad4148ff"},{"version":"1.0.0-rc01","sha1":"3672960ee939284be203d564f8ef859deae455ad"},{"version":"1.0.0-rc02","sha1":"88e3d073b797534aa59c2416ff5095be7c0e395d"},{"version":"1.0.0","sha1":"eabb75f549c6a6ee4011e15adf04bf8ca33d3762"},{"version":"1.0.1","sha1":"33d1eb71849dffbad12add134a25eb63cad4a1eb"},{"version":"1.1.0-alpha01","sha1":"5a237d3e63cbbc1bce84c9a050b2749c16ca1f73"},{"version":"1.1.0-beta01","sha1":"b62dd927ea5c1aa9e3aa44e4d351aba6d161f73f"},{"version":"1.1.0-beta02","sha1":"445033e1b49ccf8611eeb2022fa655372abc6689"},{"version":"1.1.0-rc01","sha1":"b3612e242f283b5cc510b842997bf20daf7345ec"}]}]},{"group":"androidx.heifwriter","update_time":-1,"packages":[{"package":"heifwriter","versions":[{"version":"1.0.0-alpha1","sha1":"82d9cb2c80043d246e22b61a8d032d63f21e0f8f"},{"version":"1.0.0-alpha3","sha1":"a0f39cdfe86a15bdd561d52bde58536b3d344023"},{"version":"1.0.0-beta01","sha1":"1129b6fb9e153034cd26e2c5a46a1ca10b388001"},{"version":"1.0.0-rc01","sha1":"2e2b9bad691afb35fc75ee44d14c95720dde2469"},{"version":"1.0.0-rc02","sha1":"eda34a4c9f6c2159e098d0033682839d24ceecf"},{"version":"1.0.0","sha1":"e053b3fa45d350d3913fe19adec51b3c15f61347"}]}]},{"group":"androidx.transition","update_time":-1,"packages":[{"package":"transition","versions":[{"version":"1.0.0-alpha1","sha1":"44aeeb99406357ed4cc97f4e5a60353bce5ccb56"},{"version":"1.0.0-alpha3","sha1":"bb38c2f32c07d5d3b342138eb16e4848ad934e15"},{"version":"1.0.0-beta01","sha1":"95c3b86713fdc322c2d3f276e7260f580f8e63e2"},{"version":"1.0.0-rc01","sha1":"f87605541c6dfd1a7492c4694052261fec0403fa"},{"version":"1.0.0-rc02","sha1":"a226820d1c6b8bb45f8b95a2f8fbbb5038368c21"},{"version":"1.0.0","sha1":"f3556ce8f251984acb24014c3ba055ff235929ff"},{"version":"1.0.1","sha1":"b719bbba568d6b69d4748fd56c2be914d5747558"},{"version":"1.1.0-alpha01","sha1":"40ac5aecafa7c215aed7d25008295b8a58e1a264"},{"version":"1.1.0-alpha02","sha1":"2ac2e3270a913decf1f8648413568cab942c4d1c"},{"version":"1.1.0-beta01","sha1":"ffe57a3b29153a2513f28bf03482495ea690e910"},{"version":"1.1.0-rc01","sha1":"12df95881e69abfd3583bf3942e8158ea7c1cdba"},{"version":"1.1.0-rc02","sha1":"76fe7c93a7cd09487162582a34b3b8a6a31b3a54"},{"version":"1.1.0","sha1":"ee403adc3ae340af6461cdd8151b4518c9f5c1b0"},{"version":"1.2.0-alpha01","sha1":"1bd003d9f513f4fa8aadc287e308841f15899e39"},{"version":"1.2.0-beta01","sha1":"f70df49f40ac1c521d9739b875f7e9bf4236c324"}]}]},{"group":"androidx.print","update_time":-1,"packages":[{"package":"print","versions":[{"version":"1.0.0-alpha1","sha1":"be5a4e188fabfd0efbeac2e67240594a65245860"},{"version":"1.0.0-alpha3","sha1":"442dafb3423afad10e8391ba3d623b6286630222"},{"version":"1.0.0-beta01","sha1":"f9d991b1254d805cf8c77e262952cafbc1b5a802"},{"version":"1.0.0-rc01","sha1":"316b4e58249fb75ae17568e1d9ce0199ec9f3c10"},{"version":"1.0.0-rc02","sha1":"747542fcff7e47d23e7ed568a3b93479115998a3"},{"version":"1.0.0","sha1":"7722094652c48ebe27acc94d74a55e759e4635ff"}]}]},{"group":"androidx.viewpager","update_time":-1,"packages":[{"package":"viewpager","versions":[{"version":"1.0.0-alpha1","sha1":"e771b9d2fa825ec8237abdf9ef7bd74b351067e2"},{"version":"1.0.0-alpha3","sha1":"7f56f644a262625a4307afe4f07065c939f70ffe"},{"version":"1.0.0-beta01","sha1":"1c7000bb2f81289b560b5e69083a540431d47942"},{"version":"1.0.0-rc01","sha1":"a344d78757446a6e4d7e7de224f7a9557001cb48"},{"version":"1.0.0-rc02","sha1":"42ed38a1a4a493a936ea5c00ef5048acd91ff701"},{"version":"1.0.0","sha1":"1f90e13820f96c2fb868f9674079a551678d68b2"}]}]},{"group":"androidx.annotation","update_time":-1,"packages":[{"package":"annotation","versions":[{"version":"1.0.0-alpha1","sha1":"fa86eaa79396c7eb0e1706ffcf6e029cab0f1a38"},{"version":"1.0.0-alpha3","sha1":"690fd80bcdb0fc4a52f9b07ed599dfe5a6029dd2"},{"version":"1.0.0-beta01","sha1":"45599f2cd5965ac05a1488fa2a5c0cdd7c499ead"},{"version":"1.0.0-rc01","sha1":"d3593590c18777c0721a538a06c6882621ab49fe"},{"version":"1.0.0-rc02","sha1":"d3593590c18777c0721a538a06c6882621ab49fe"},{"version":"1.0.0","sha1":"45599f2cd5965ac05a1488fa2a5c0cdd7c499ead"},{"version":"1.0.1","sha1":"2dfd8f6b2a8fc466a1ae4e329fb79cd580f6393f"},{"version":"1.0.2","sha1":"2f1d597d48e5309e935ce1212eedf5ae69d3f97"},{"version":"1.1.0-alpha01","sha1":"7740fa3bdddfdbf7b05d2483987a11c3da1d052c"},{"version":"1.1.0-alpha02","sha1":"11d7b1518f8e1044319820cb2568fd2f59818b26"},{"version":"1.1.0-beta01","sha1":"e3a6fb2f40e3a3842e6b7472628ba4ce416ea4c8"},{"version":"1.1.0-rc01","sha1":"e3a6fb2f40e3a3842e6b7472628ba4ce416ea4c8"},{"version":"1.1.0","sha1":"e3a6fb2f40e3a3842e6b7472628ba4ce416ea4c8"}]}]},{"group":"androidx.exifinterface","update_time":-1,"packages":[{"package":"exifinterface","versions":[{"version":"1.0.0-alpha1","sha1":"435e54b2d5d2756905604cfab430dab1bef0a793"},{"version":"1.0.0-alpha3","sha1":"24c5eac46a3a3b258d946343fd4915bb9921f929"},{"version":"1.0.0-beta01","sha1":"4d11abefd4154998666c248122f9a02c159a1cfb"},{"version":"1.0.0-rc01","sha1":"1fc8fb9edd5cf949d6b11951f58fe1f190be74dd"},{"version":"1.0.0-rc02","sha1":"8a97e0894574220fad84232c4d34c72ff328c17b"},{"version":"1.0.0","sha1":"1ab12ceadd49f94006e1ec0ea5fa51b701169cd5"},{"version":"1.1.0-alpha01","sha1":"7ace7eecf53b8c51637fd3a25f52ced584bfc6e4"},{"version":"1.1.0-beta01","sha1":"3a3ae85030468e63b28989401fe69db581e40c82"}]}]},{"group":"androidx.dynamicanimation","update_time":-1,"packages":[{"package":"dynamicanimation","versions":[{"version":"1.0.0-alpha1","sha1":"9298437bc6403977050b8d3de63fd45d2a5d93ff"},{"version":"1.0.0-alpha3","sha1":"6805dccb153710eed5cbfe099b1fa64d96ae5ed0"},{"version":"1.0.0-beta01","sha1":"e738b7d5d6fa93b6d6c44bc0c725a2a9aa1dee9f"},{"version":"1.0.0-rc01","sha1":"c107603b697b7235d70457ecd2acf199fe149e69"},{"version":"1.0.0-rc02","sha1":"fc518b578e2cf3a39471a417982f70e0e70db614"},{"version":"1.0.0","sha1":"e980497a58fb319d471f3bb78d066b926a08ba8e"},{"version":"1.1.0-alpha01","sha1":"f78d654054916393c2595ce28f3f8b72a949cfe3"},{"version":"1.1.0-alpha02","sha1":"5ac9cd962e19f26521a943d16db765bb701e2f73"}]},{"package":"dynamicanimation-ktx","versions":[{"version":"1.0.0-alpha01","sha1":"c6dc929ecc64462035dba75531ae8a3dfe2dbc02"},{"version":"1.0.0-alpha02","sha1":"3c72ce125c9fa6af4dd8e8f36329a648d185236e"}]}]},{"group":"androidx.browser","update_time":-1,"packages":[{"package":"browser","versions":[{"version":"1.0.0-alpha1","sha1":"9287330ac0b6ea1d6fcc7727485fcffb5966d103"},{"version":"1.0.0-alpha3","sha1":"fd6d79dfd70f0785e8c7a71520ed49abaffb41b2"},{"version":"1.0.0-beta01","sha1":"18b738ff1d0f17512e420039f17187701919f38d"},{"version":"1.0.0-rc01","sha1":"1aee98a1a91dc2fd0ee85d4d918b19e9a9db3ac1"},{"version":"1.0.0-rc02","sha1":"cac2bb37df008985aa8910f89ff6444fbb931548"},{"version":"1.0.0","sha1":"c7b63c6b23d0f3080adda99145248901b5921e4f"}]}]},{"group":"androidx.localbroadcastmanager","update_time":-1,"packages":[{"package":"localbroadcastmanager","versions":[{"version":"1.0.0-alpha1","sha1":"4569fec7b2417d69ed2eb568ffedec7b0444d97"},{"version":"1.0.0-alpha3","sha1":"7c7e16c059f6f1978a014688381cfd98c7703111"},{"version":"1.0.0-beta01","sha1":"e7cf2986b144eae5faf96a69dfdf8eac5b7b424d"},{"version":"1.0.0-rc01","sha1":"81664d602e7bcdf7fe894697e7228d3ff1f4394a"},{"version":"1.0.0-rc02","sha1":"3ce819572e4f7e89dc1a0ddd8aaa1d502e7e40f5"},{"version":"1.0.0","sha1":"2734f31c8321e83ce6b60570d14777fc33cc2ece"},{"version":"1.1.0-alpha01","sha1":"6cfa54e927f87084874547617a41e067005ba686"}]}]},{"group":"androidx.asynclayoutinflater","update_time":-1,"packages":[{"package":"asynclayoutinflater","versions":[{"version":"1.0.0-alpha1","sha1":"41c1ee5858921d0033bf9bf4e52f25e7d9de4180"},{"version":"1.0.0-alpha3","sha1":"b235f9d978691855a5dd30da12b3d76472f85b5d"},{"version":"1.0.0-beta01","sha1":"2b693e65fa6a347c03c980256283b26d52dc5652"},{"version":"1.0.0-rc01","sha1":"438fea67007c7808ae257373e82da22a71c98f06"},{"version":"1.0.0-rc02","sha1":"7ca9f74553310db5d41a3d490b5fb2f5698eaa3a"},{"version":"1.0.0","sha1":"5ffa788d19a6863799f25cb50d4fdfb0ec649037"}]}]},{"group":"androidx.contentpager","update_time":-1,"packages":[{"package":"contentpager","versions":[{"version":"1.0.0-alpha1","sha1":"99f00245e78e86fca69005d7b1c76d361dd297a8"},{"version":"1.0.0-alpha3","sha1":"31f25fb26cae31e91eea183b8e5c5c11babffa7"},{"version":"1.0.0-beta01","sha1":"8e42628be6391ffe98eef41a09eefd22a3840878"},{"version":"1.0.0-rc01","sha1":"c0d7d9882c786e9da2f16c8ad0327af3c03f83c5"},{"version":"1.0.0-rc02","sha1":"486d8a9200e6c9fd7f8dea9cd534fb454fa734ed"},{"version":"1.0.0","sha1":"65c427380f1b6b66afef63fe2b596d6261e5ab34"}]}]},{"group":"androidx.slidingpanelayout","update_time":-1,"packages":[{"package":"slidingpanelayout","versions":[{"version":"1.0.0-alpha1","sha1":"a4bb5f1737ca16c6abaeaf0a6c8018a4b6009c08"},{"version":"1.0.0-alpha3","sha1":"979ef1130e8a70f0991d060130b91a6ac053ede5"},{"version":"1.0.0-beta01","sha1":"47e8ed73c71aa7815f300046e1451d53de1e85fe"},{"version":"1.0.0-rc01","sha1":"52935cc725d7e2e7fbef15293de14583351b70eb"},{"version":"1.0.0-rc02","sha1":"e66081c915e18a6bc33aba2688d52cfb02496b73"},{"version":"1.0.0","sha1":"37eba9ccbf09b75cc4aa78a5e182d5b8ba79ad6a"}]}]},{"group":"androidx.cursoradapter","update_time":-1,"packages":[{"package":"cursoradapter","versions":[{"version":"1.0.0-alpha1","sha1":"627ec6ec093b41bd2a708185753b10aeb1aa72c7"},{"version":"1.0.0-alpha3","sha1":"16c71d5046bfbe32b9dee33b6ee81c45b832b6a"},{"version":"1.0.0-beta01","sha1":"fe79998e2e6fbf9f275ad7669788cdba9ffcd43e"},{"version":"1.0.0-rc01","sha1":"a99fd54f26c3ef9ee7b09319c25baadecc3bddf"},{"version":"1.0.0-rc02","sha1":"4824826e542527be79bb4c00b65b088f359da686"},{"version":"1.0.0","sha1":"74014983a86b83cbce534dec4e7aa9312f5f5d82"}]}]},{"group":"androidx.media","update_time":-1,"packages":[{"package":"media-widget","versions":[{"version":"1.0.0-alpha1","sha1":"be446634b49e3415a779375270a0ffdc4810201b"},{"version":"1.0.0-alpha3","sha1":"196d9f2cc00e8472682fc71153499a6283510b53"},{"version":"1.0.0-alpha4","sha1":"71796052ef0ffd59578361dab2ac95ffbfaa246e"},{"version":"1.0.0-alpha5","sha1":"4157bf3cf55d257aca21995a18416ebe32e5619c"},{"version":"1.0.0-alpha06","sha1":"780d4fd0364ad6e015fb5a6ad9d0f2bda6758cc9"}]},{"package":"media","versions":[{"version":"1.0.0-alpha1","sha1":"94846c1cc4aa7c21fab7488456ec0fb2e993f330"},{"version":"1.0.0-alpha3","sha1":"e95b47824162998208ff21c95412408e31665d6f"},{"version":"1.0.0-beta01","sha1":"4f23f4c8af9ef27b1b060550477f4828d7e47c2f"},{"version":"1.0.0-rc01","sha1":"ccc4ff9c14a4e9357d4e9a1e2745b3decfea07c"},{"version":"1.0.0-rc02","sha1":"4572ed3d6abb34bb82fa8fed57a8cc44cdd0a996"},{"version":"1.0.0","sha1":"7f92bbaf670497a9a1105124122fb20cee61e58c"},{"version":"1.0.1","sha1":"bd9a10584f1e47a78fb269f1c29763c8f4ec891b"},{"version":"1.1.0-alpha01","sha1":"d6a0f7bfb8bd5157585d9bf35b8f57ff95241640"},{"version":"1.1.0-alpha02","sha1":"af7b1de520015613f3812974def9431f8e71879d"},{"version":"1.1.0-alpha03","sha1":"2b6c0a076479027239cee4d1374e37c8e71262b4"},{"version":"1.1.0-alpha04","sha1":"530a05f4dfbf412781091a89ef8040793caf3de7"},{"version":"1.1.0-beta01","sha1":"17f22661bddf41f84b12c1bb103f95e72495938f"},{"version":"1.1.0-beta02","sha1":"a90baa620053d8f013a284be4ddd919a9dd2eeb6"},{"version":"1.1.0-rc01","sha1":"ae3a9be3d84ca690240efc178c389a6e9050c6da"}]}]},{"group":"androidx.loader","update_time":-1,"packages":[{"package":"loader","versions":[{"version":"1.0.0-alpha1","sha1":"fc2c5476086a471abc2b3d5542ea784993332c86"},{"version":"1.0.0-alpha3","sha1":"e71e0c684af1775263695962ea6e5f0486b90fc4"},{"version":"1.0.0-beta01","sha1":"cf69ed4b46a24da546d2567428c7e1c48247a39b"},{"version":"1.0.0-rc01","sha1":"d1c87794bbad9c5f0d0b151c746c0bc0d19c7b34"},{"version":"1.0.0-rc02","sha1":"bfd3e2dc2db2b9cdc8b38f8e048e6229596e206"},{"version":"1.0.0","sha1":"8af8b6cec0da85c207d03e15840e0722cbc71e70"},{"version":"1.1.0-alpha01","sha1":"b6d0426d5725a6fc11e462ee95fccb5ada7b2528"},{"version":"1.1.0-beta01","sha1":"7e7f3f6910b9408f24f2cb34cd0fc21a28bafc85"},{"version":"1.1.0-rc01","sha1":"f6735e35e9881ceaccceddece3ef322a7a13106b"}]}]},{"group":"androidx.interpolator","update_time":-1,"packages":[{"package":"interpolator","versions":[{"version":"1.0.0-alpha1","sha1":"300bd7f444fc58b6957049a4920a5cdc244b7c9c"},{"version":"1.0.0-alpha3","sha1":"7520c548608f9eaf92b2539f1fc69492e711a51f"},{"version":"1.0.0-beta01","sha1":"462d6076e1b73eb0437559f25d9634b964cf885"},{"version":"1.0.0-rc01","sha1":"72a792926a12ff99d751aa17cb7101c631e895bc"},{"version":"1.0.0-rc02","sha1":"2782e0929bc915a34f0a7187fd7fc34c68092eb"},{"version":"1.0.0","sha1":"8a01fa254a23b9388571eb6334b03707c7d122d7"}]}]},{"group":"androidx.coordinatorlayout","update_time":-1,"packages":[{"package":"coordinatorlayout","versions":[{"version":"1.0.0-alpha1","sha1":"66cc8e65b1d60355418ccb9418b7bd0b406e592c"},{"version":"1.0.0-alpha3","sha1":"652ee14f417dde659a43bd0f7acf4df7a825eb54"},{"version":"1.0.0-beta01","sha1":"312868e90c5276a77baef3ba47b07ebd916894f2"},{"version":"1.0.0-rc01","sha1":"307c8ef54301a600d80bd314c3277056fccdab5a"},{"version":"1.0.0-rc02","sha1":"bbf37b2908169d7d5af83df0fe41f600ffe21d6b"},{"version":"1.0.0","sha1":"7664385a7e39112b780baf8819ee880dcd3c4094"},{"version":"1.1.0-alpha01","sha1":"89ece9ec56d664a0f6067306995b8964c8d3d619"},{"version":"1.1.0-beta01","sha1":"79ee941dc920601cf7e211a17727780c80bb0145"}]}]},{"group":"androidx.fragment","update_time":-1,"packages":[{"package":"fragment-ktx","versions":[{"version":"1.0.0-alpha1","sha1":"864fb74f469ab21fce783bf93080034db9e06f56"},{"version":"1.0.0-alpha3","sha1":"a2a1dca8fed04dcb66332a4ef1f891514838e686"},{"version":"1.0.0-beta01","sha1":"64c68ac5d21a0744b677dd517e43a7c6d7232500"},{"version":"1.0.0-rc01","sha1":"d61849ff328a45818a536a7ca077122588ebf35d"},{"version":"1.0.0-rc02","sha1":"153c8525b37741a27c786c48aff7bc77aff02d4e"},{"version":"1.0.0","sha1":"62c14781df02ecac21273883db2428a61afbc538"},{"version":"1.1.0-alpha01","sha1":"ae42294c85f18662a8f798aae78da78630268aac"},{"version":"1.1.0-alpha02","sha1":"bb7aa08a624595a7292e6949755c90184561c848"},{"version":"1.1.0-alpha03","sha1":"ccdabc5ca45c8735e87b19b854b25b52f9e1422f"},{"version":"1.1.0-alpha04","sha1":"cf99e77aa592edc6c7fc4e077eaf953a2b57c672"},{"version":"1.1.0-alpha05","sha1":"d6b85321a2acc8ca4d670defd383e74ec732d2aa"},{"version":"1.1.0-alpha06","sha1":"6ed1173c6f170c84a39f61e7afbe3d047aa02465"},{"version":"1.1.0-alpha07","sha1":"fdc931f5b3462abf56a48c119ceeea327e2061ca"},{"version":"1.1.0-alpha08","sha1":"4669f13867908adcf89932caca87443a1476bbec"},{"version":"1.1.0-alpha09","sha1":"91af73c77a8941536689ac91e4169cc5b52736f6"},{"version":"1.1.0-beta01","sha1":"15d1825cd0b5ad5f925ce0d3517ef20cc81c0fe8"},{"version":"1.1.0-rc01","sha1":"80c3aee5b4b4a92293f713218e9df6f8bff36adb"},{"version":"1.2.0-alpha01","sha1":"a793c15e41ac6d7a221df8d6272c1d4b5d543414"}]},{"package":"fragment","versions":[{"version":"1.0.0-alpha1","sha1":"39d40c687c0ccf41cd1fade2592e9678a916329f"},{"version":"1.0.0-alpha3","sha1":"207f0a380eb098433a314cbda077320bc9c5a2fa"},{"version":"1.0.0-beta01","sha1":"97c1e1cde3b3aca70a7b35c551c9bbc6c28015f8"},{"version":"1.0.0-rc01","sha1":"e3148956b0812057b21463c4c7aaad2b546af947"},{"version":"1.0.0-rc02","sha1":"351a9a76d48d2b950580beb71eb4928fe5b340c8"},{"version":"1.0.0","sha1":"b40f6a2ae814f72d1e71a5df6dc1283c00cd52f"},{"version":"1.1.0-alpha01","sha1":"f833ab91beba2ea179a1eb6a40e44a79863e9284"},{"version":"1.1.0-alpha02","sha1":"9c609ed2184c89c20b3c9c6838d3ad1d5dedbc0a"},{"version":"1.1.0-alpha03","sha1":"346ee20936a100062644b2846be2b6c214782d57"},{"version":"1.1.0-alpha04","sha1":"779b74e6a86cbc96b3e1d8ba3fb0c97d37739c32"},{"version":"1.1.0-alpha05","sha1":"5e3d37950e3ee834db92d486b5b3019176047cc8"},{"version":"1.1.0-alpha06","sha1":"3ebe8433f8a1d05c6c05f38041b9ae60b58ece83"},{"version":"1.1.0-alpha07","sha1":"ee9e77ef388085302cc26dccce5c0d0462a68e08"},{"version":"1.1.0-alpha08","sha1":"e6e638f9c2351aacc888743da869b5cccfaaeec6"},{"version":"1.1.0-alpha09","sha1":"f8434f5f184e65da16e6611e46dd4a50e0de2076"},{"version":"1.1.0-beta01","sha1":"cba255370b9f7dabbb0b928bd8ee659419ff93d8"},{"version":"1.1.0-rc01","sha1":"5369151f89369269c30897029bed5cf258509f60"},{"version":"1.2.0-alpha01","sha1":"9cacc110702a53ac5ce48d8294ff04fd2df30560"}]},{"package":"fragment-testing","versions":[{"version":"1.1.0-alpha01","sha1":"4b9f47627455857f6cdb62aa6339734470223ed9"},{"version":"1.1.0-alpha02","sha1":"57fae95e9bf408d4592ffa959feae73d0029c553"},{"version":"1.1.0-alpha03","sha1":"542f0c8735a79223338b3909598836ab3e184131"},{"version":"1.1.0-alpha04","sha1":"f89ed3713b489da6e2d9f15e5e9128515305b539"},{"version":"1.1.0-alpha05","sha1":"b4b7900540830d8520c39d0696404f39ad9f0b2e"},{"version":"1.1.0-alpha06","sha1":"55a0350d24cc040aeba69b76dbd74aba63d6c288"},{"version":"1.1.0-alpha07","sha1":"3666a877acd250d6d48a4dfb3845f2a85c09ed05"},{"version":"1.1.0-alpha08","sha1":"b56506137a9c765497e1452a808587a70407b5b"},{"version":"1.1.0-alpha09","sha1":"f31791bad7811c6c31c32741f4122e3f50e2ec77"},{"version":"1.1.0-beta01","sha1":"1d93e3c86e6d50606a204a214f51307c4ea100fd"},{"version":"1.1.0-rc01","sha1":"24895bee8db781608486c0f6e0e8435061f7bb9e"},{"version":"1.2.0-alpha01","sha1":"309aec734e6fee7dc4536cb08dbe8b2dfe648cf2"}]}]},{"group":"androidx.tvprovider","update_time":-1,"packages":[{"package":"tvprovider","versions":[{"version":"1.0.0-alpha1","sha1":"6cde8cc55705361ba7e03664db1d1894ddf3bce3"},{"version":"1.0.0-alpha3","sha1":"1350cf319899e49579713ca212221a4e8a8fc36c"},{"version":"1.0.0-beta01","sha1":"7b6f7b69fd01fae2fd8ff5aef40354e5e27feec0"},{"version":"1.0.0-rc01","sha1":"347478175fbddcecfa2a5b41a2cc2bdc050891a"},{"version":"1.0.0-rc02","sha1":"2eca72ab7290c232021492002159b96d98b5f5ba"},{"version":"1.0.0","sha1":"b8248df886bbf1c86571baccd290e0f3b287fdee"}]}]},{"group":"androidx.slice","update_time":-1,"packages":[{"package":"slice-core","versions":[{"version":"1.0.0-alpha1","sha1":"c778cdba5084751ffde3e4edaca54997d727df7d"},{"version":"1.0.0-alpha2","sha1":"1e1e96a183ce9097e8d17473fba8e51f5ac490df"},{"version":"1.0.0-alpha3","sha1":"99761351b88f02bbb316296f1b603c58e96c526c"},{"version":"1.0.0-beta01","sha1":"5244ac529c345cdc3f838ea4cc45a093eda95ff3"},{"version":"1.0.0-rc01","sha1":"84bd1878a4a6491cbbabc9eed58184ad4fa2c64e"},{"version":"1.0.0-rc02","sha1":"f92c091fcadd60cdcd2d7ee9d00465aad3bfffce"},{"version":"1.0.0","sha1":"5639d2525b5b8eb290d89d166c8108358b32dffe"},{"version":"1.1.0-alpha01","sha1":"8ad4cc15d0ca3d7008fd166dc727dbfeaf060a5b"}]},{"package":"slice-builders","versions":[{"version":"1.0.0-alpha1","sha1":"598ef81c0d36c614d69093dec6a4752a52e6f59d"},{"version":"1.0.0-alpha2","sha1":"fc85e54cfc2c7f6953c5a6a80de0544c2742d2da"},{"version":"1.0.0-alpha3","sha1":"e851998b2e71510f4587709a1fda506e03687743"},{"version":"1.0.0-beta01","sha1":"16d3b92a55df24473f0bfbf88417fc7260d79e7a"},{"version":"1.0.0-rc01","sha1":"a5b6f4c090a0a773836b7b24221b9960e5e1e43d"},{"version":"1.0.0-rc02","sha1":"771a9fd58414d4ad1bbb981077d76540e0c5223c"},{"version":"1.0.0","sha1":"b7c0588402ba70b534ff01f77ae2b19a98897c27"},{"version":"1.1.0-alpha01","sha1":"45daffc4792cdfd14dee0b5bcccd5eddf88d58cc"}]},{"package":"slice-view","versions":[{"version":"1.0.0-alpha1","sha1":"b1f97bac5aaf750cb5888600e12330968f8620fa"},{"version":"1.0.0-alpha2","sha1":"2e80b4a9ab38e59d5ac9e4e702f7ef4752836074"},{"version":"1.0.0-alpha3","sha1":"ebc28be24f0e6fbe28707da147fec833ecf85fcb"},{"version":"1.0.0-beta01","sha1":"9b445fc52a75ccd35e376fa3ebbfbbb8e4990218"},{"version":"1.0.0-rc01","sha1":"6f2f24baa47cb79474a219b042d44f2ca7f3388d"},{"version":"1.0.0-rc02","sha1":"cb5c440aee4518173d750126a2ba9642ee2dbd20"},{"version":"1.0.0","sha1":"f08a9c9fd4ed318712be108e8fc2d0daf316242d"},{"version":"1.1.0-alpha01","sha1":"1ba75f994976cc32ded21dee5bcaf14906b1dd32"}]},{"package":"slice-builders-ktx","versions":[{"version":"1.0.0-alpha3","sha1":"b4ded1ed60f48958b55f058e4a9502ce6b3fc68"},{"version":"1.0.0-alpha4","sha1":"ec6562b06d5ec17009f259815c41eb60a31c8688"},{"version":"1.0.0-alpha5","sha1":"e882b74ee8a36fa2a1e2c309ba54903cd79f15b6"},{"version":"1.0.0-alpha6","sha1":"42b087f9bc445cc4f92c39ba4ce6ec3627db34b6"},{"version":"1.0.0-alpha07","sha1":"36f44e60c9306f4f985c46b97d07779f0f5264b7"}]}]},{"group":"androidx.collection","update_time":-1,"packages":[{"package":"collection-ktx","versions":[{"version":"1.0.0-alpha1","sha1":"bb22a30fe82cf6442434d7d1b6e6247b263aba85"},{"version":"1.0.0-alpha3","sha1":"41bb880d58e4e61ffc2f1263bc6ce4218d323799"},{"version":"1.0.0-beta01","sha1":"c85178f21c9b663c0d68177985027394fb0324b4"},{"version":"1.0.0-rc01","sha1":"6338ec1a98e91cd30d22b6f25540cce9e4e9f2f1"},{"version":"1.0.0-rc02","sha1":"6338ec1a98e91cd30d22b6f25540cce9e4e9f2f1"},{"version":"1.0.0","sha1":"c85178f21c9b663c0d68177985027394fb0324b4"},{"version":"1.1.0-alpha01","sha1":"ff8d37573c827e01703c7123607be7bcbd7190ad"},{"version":"1.1.0-alpha02","sha1":"ff8d37573c827e01703c7123607be7bcbd7190ad"},{"version":"1.1.0-alpha03","sha1":"ff8d37573c827e01703c7123607be7bcbd7190ad"},{"version":"1.1.0-beta01","sha1":"ff8d37573c827e01703c7123607be7bcbd7190ad"},{"version":"1.1.0-rc01","sha1":"f807b2f366f7b75142a67d2f3c10031065b5168"},{"version":"1.1.0","sha1":"f807b2f366f7b75142a67d2f3c10031065b5168"}]},{"package":"collection","versions":[{"version":"1.0.0-alpha1","sha1":"e412a46ff4788df834a34d632f2dd95436f2831e"},{"version":"1.0.0-alpha3","sha1":"b45254ae99b8fa4b3d7a3616490c0dfd631a9667"},{"version":"1.0.0-beta01","sha1":"42858b26cafdaa69b6149f45dfc2894007bc2c7a"},{"version":"1.0.0-rc01","sha1":"a6f2dcbb844fe194f0ea37bc327a3ffab0de9e4f"},{"version":"1.0.0-rc02","sha1":"a6f2dcbb844fe194f0ea37bc327a3ffab0de9e4f"},{"version":"1.0.0","sha1":"42858b26cafdaa69b6149f45dfc2894007bc2c7a"},{"version":"1.1.0-alpha01","sha1":"b0dc394c09edd6fe0539bd2587e7d24ee954fdcf"},{"version":"1.1.0-alpha02","sha1":"4a15b3969c1392eb8413c6c734337f0bd2cd29d0"},{"version":"1.1.0-alpha03","sha1":"7c8e35b4d0933887a7047e5fcc43fd52045dcd54"},{"version":"1.1.0-beta01","sha1":"1f27220b47669781457de0d600849a5de0e89909"},{"version":"1.1.0-rc01","sha1":"1f27220b47669781457de0d600849a5de0e89909"},{"version":"1.1.0","sha1":"1f27220b47669781457de0d600849a5de0e89909"}]}]},{"group":"androidx.recommendation","update_time":-1,"packages":[{"package":"recommendation","versions":[{"version":"1.0.0-alpha1","sha1":"69c0bc1437fae690245e00332fd2316594296b11"},{"version":"1.0.0-alpha3","sha1":"d0b897c19c9a8bcbe41437d3970e69785698d298"},{"version":"1.0.0-beta01","sha1":"f6cd834318a0c9fd3cfe652be9efc4aeacfc3d14"},{"version":"1.0.0-rc01","sha1":"2d6e395461c41649c9b723fba4886d99f2fa5590"},{"version":"1.0.0-rc02","sha1":"9701da1d8606c84ad206d3778f816ac28d10db43"},{"version":"1.0.0","sha1":"98b0430fd82e4883c4951ebedfb1d99ffce81b9a"}]}]},{"group":"androidx.drawerlayout","update_time":-1,"packages":[{"package":"drawerlayout","versions":[{"version":"1.0.0-alpha1","sha1":"b2df1bdd10ab65bfc47377c20a87e5548d7ac423"},{"version":"1.0.0-alpha3","sha1":"209db3cd3bc661408992118e82ed77f48a8077b3"},{"version":"1.0.0-beta01","sha1":"c57b2c1f40a8773c99d855daf7092861ab24330a"},{"version":"1.0.0-rc01","sha1":"11697443a01444a1a4a1918d8055bb1c729bfbe3"},{"version":"1.0.0-rc02","sha1":"89b60f6cbcc55e67267036d5cdbedd9562640d4b"},{"version":"1.0.0","sha1":"dd02c7e207136e1272b33815cc61e57676ed13a2"},{"version":"1.1.0-alpha01","sha1":"98ffa44b2d4427d218d71dbf3ca7a4a6f9408366"},{"version":"1.1.0-alpha02","sha1":"57fc101cd036cf6a7a7684dafe2fda29047a8463"}]}]},{"group":"androidx.recyclerview","update_time":-1,"packages":[{"package":"recyclerview-selection","versions":[{"version":"1.0.0-alpha1","sha1":"8bb0b8d9279a31a26fb2bf8613f043b6d078e461"},{"version":"1.0.0-alpha3","sha1":"7c83b2f628ad7968bc1ae6ca54e4417f1908a55b"},{"version":"1.0.0-beta01","sha1":"3e93e78836c15532195812f486fe556dce62abe6"},{"version":"1.0.0-rc01","sha1":"b3f5d241d08954130399996cc3e2e0fe4e34540"},{"version":"1.0.0-rc02","sha1":"17ffa5f6aae8613b1291e81a9da2da8d8a8cecf5"},{"version":"1.0.0","sha1":"259f429e62bd07391c7044ab89137c85cfc8aa5d"},{"version":"1.1.0-alpha01","sha1":"c86ab07949ab08f428ceef8e4e9bfc69ea47b31a"},{"version":"1.1.0-alpha05","sha1":"65995c7ed07ad7c4254f2c837e9348c4e699915c"},{"version":"1.1.0-alpha06","sha1":"4255213b7d9100ab8f60ee98b7cc69cca3612af4"}]},{"package":"recyclerview","versions":[{"version":"1.0.0-alpha1","sha1":"e24ea2a96f281d59a3f74152d05818a7a17f57f9"},{"version":"1.0.0-alpha3","sha1":"40c85a39d4fa3ad4f2867660dc3f562fbb875202"},{"version":"1.0.0-beta01","sha1":"6c61bbd868936300fa60e192136f46dd0451f099"},{"version":"1.0.0-rc01","sha1":"209aaeb8be77829123e3eaa07e2d899621a74a71"},{"version":"1.0.0-rc02","sha1":"ab86305fc011a9e35cfcadb919bdca536112c5c5"},{"version":"1.0.0","sha1":"ee3a2ad35bcfa91ab9eeebe991b0893ab40009cf"},{"version":"1.1.0-alpha01","sha1":"534eeeb9df5aa89b6040d3e67b727de77758a455"},{"version":"1.1.0-alpha02","sha1":"a604d422d56e73b4d8eeb73c8e6a069a89fa2ec3"},{"version":"1.1.0-alpha03","sha1":"5a0ee63e577ae6f475db145a7942b627465181a7"},{"version":"1.1.0-alpha04","sha1":"58b442ef1fca67379a229a3188dd71fcd41732f5"},{"version":"1.1.0-alpha05","sha1":"31b93d411abe017223dcc9dbb153687e99ae6024"},{"version":"1.1.0-alpha06","sha1":"503cd5b150cc3fb46d4abb54a2bc7922b19c617a"},{"version":"1.1.0-beta01","sha1":"d976edc8682b3d4ff12ce4828c8fa30eee8a12cd"}]}]},{"group":"androidx.webkit","update_time":-1,"packages":[{"package":"webkit","versions":[{"version":"1.0.0-alpha1","sha1":"224a51b3f349a56584f0058d707ad04423e4b254"},{"version":"1.0.0-alpha3","sha1":"92f68ff270d9624b0a61d1cca25fb272428382c8"},{"version":"1.0.0-beta01","sha1":"ab004d4a8755bdb5b6d9fb158301a8c24b9eaf2e"},{"version":"1.0.0-rc01","sha1":"2c80aee2732bde1c4ab77662e9fc40e731e814db"},{"version":"1.0.0-rc02","sha1":"4083fa62b14026d19419347564009df1278783c8"},{"version":"1.0.0","sha1":"80e8448f19659283b6e99931107a866937f7e9ed"},{"version":"1.1.0-alpha01","sha1":"81eeb3576f592828119442fd954b4adff43595d1"}]}]},{"group":"androidx.palette","update_time":-1,"packages":[{"package":"palette-ktx","versions":[{"version":"1.0.0-alpha1","sha1":"560206f1dc2a1a1605b6778cca4d38e8828acaba"},{"version":"1.0.0-alpha3","sha1":"3a502a2d7082c7acc38ab97455a9fa6e54d4f656"},{"version":"1.0.0-beta01","sha1":"bf5d5996386b96509bcb04864e10e49599812d07"},{"version":"1.0.0-rc01","sha1":"e3ccc7ee254deab17b71117f7f063227f7d58926"},{"version":"1.0.0-rc02","sha1":"d23ab832c5aa4fd805a6882dbc0be108ba37b1a2"},{"version":"1.0.0","sha1":"fbc21af9a551489e4405d69a3b278bd73881d1d7"}]},{"package":"palette","versions":[{"version":"1.0.0-alpha1","sha1":"c7b4604ad9c878a6d42bf5d78755d60103441106"},{"version":"1.0.0-alpha3","sha1":"b5da753a0bd8d33e23b2384effe75763efc64c8e"},{"version":"1.0.0-beta01","sha1":"12bfbb8c381b216c83b4173a078d3cc3de95129d"},{"version":"1.0.0-rc01","sha1":"fbadf559acfb584a72bfd19606fe4070d9fd68d8"},{"version":"1.0.0-rc02","sha1":"4d67ae4fcd544266485058d88c0d714b27523583"},{"version":"1.0.0","sha1":"4083c20d7fa72a36709422cee33b96b8c6e3a36c"}]}]},{"group":"com.google.ar.sceneform","update_time":-1,"packages":[{"package":"filament-android","versions":[{"version":"1.0.0","sha1":"dd4d33b72107802a222e831f0bb4c0f79ba1d26d"},{"version":"1.3.0","sha1":"b97bf4c5c4826b70e1290b117e8dabb3607c90f7"},{"version":"1.4.0","sha1":"a0dd0104e54f109cae775b0f2e2f18e671ce1d29"},{"version":"1.5.0","sha1":"f94f120f0d3a4f84a98bc74be308ae69c8b5a0f5"},{"version":"1.5.1","sha1":"f94f120f0d3a4f84a98bc74be308ae69c8b5a0f5"},{"version":"1.6.0","sha1":"fec925eae5e5f1030c3269fd2a07cae8a348cace"},{"version":"1.7.0","sha1":"267b0e97017e645bfa5a0d22d1db5f0fb324b5e5"},{"version":"1.8.0","sha1":"4cfa95c643748fb4666a29d04dc724621cbe943a"},{"version":"1.9.0","sha1":"3f83f19a781085f9a52039cd35a447de2dcc283c"},{"version":"1.10.0","sha1":"e14b2a5755767bd7a9746e7a165a1ef74feda8f1"}]},{"package":"core","versions":[{"version":"1.0.0","sha1":"c10bd04f4e71f4ce64a0124e459da902ddeddd16"},{"version":"1.3.0","sha1":"21054050662db26715c71be2dd86926587569816"},{"version":"1.4.0","sha1":"e4e9fea802565174f1e704d7fa7674287b500e15"},{"version":"1.5.0","sha1":"3a897e7e008749b2638af38d386e566351cb9a8e"},{"version":"1.5.1","sha1":"f7fc947e81e698a59a34be33cd3ae3e7339c3c76"},{"version":"1.6.0","sha1":"5667e87445d45b016d7ba7eb78f3fc75c7014f35"},{"version":"1.7.0","sha1":"29fe866577f9a160a86d9a077953722e152fd3f0"},{"version":"1.8.0","sha1":"c79691c316f5d1dfedeccd923589c00b96822da4"},{"version":"1.9.0","sha1":"26a386884ae14e84b5a7c6d1553cd9a2f95b9dd1"},{"version":"1.10.0","sha1":"c5e1b6aab1faf807b8a32c8f410a79dd587bdda"}]},{"package":"rendering","versions":[{"version":"1.0.0","sha1":"c21993eab0712e9a5bae73841d04aa98989559b1"},{"version":"1.3.0","sha1":"99286aa686445674ed0ab8e0e38e8416c83d4b95"},{"version":"1.4.0","sha1":"811debe29c930cfa4b2e950a966fea191f00ad81"},{"version":"1.5.0","sha1":"7c25a826a115392dc8738b95b0dbea0c7ef276c0"},{"version":"1.5.1","sha1":"227c52ae2e71627d3b8f3cd3a6dcf3c69975df5d"},{"version":"1.6.0","sha1":"612b25da9dbe257337e464c502c19400af1fda63"},{"version":"1.7.0","sha1":"b43f276500202ff882411f9946b91c7d0e7f5cdf"},{"version":"1.8.0","sha1":"ff65cc4e0c674a54be4f081b78012b2c359a75fb"},{"version":"1.9.0","sha1":"3b6092011dd66c81ab110f510a3a9e162d5ce923"},{"version":"1.10.0","sha1":"1d0dec0cc174efbbf067213950a82f64b5f1ad2e"}]},{"package":"plugin","versions":[{"version":"1.0.0","sha1":"bb7054db4021c20dda1d6ab36087d62fb8767f8c"},{"version":"1.0.1","sha1":"63525139a115a702b17890d3a850fed88c39b607"},{"version":"1.3.0","sha1":"24804c6b38d4ab3fbd1ff0fbdc5ef841dd7c0ff5"},{"version":"1.4.0","sha1":"510f4abef4a9612367f3797cd838ab2f04f893cd"},{"version":"1.5.0","sha1":"1a59ffc6fbd7ff8ac95e208ce91e63502ff49113"},{"version":"1.5.1","sha1":"9c54d4857b0cea781acc63fb10b6120e8109d87c"},{"version":"1.6.0","sha1":"c36c8dcb6ed108ed13c689a27a9426111277ee79"},{"version":"1.7.0","sha1":"48508b7b609f5be97ad7cdabc65c985c3b189066"},{"version":"1.8.0","sha1":"cae0fecf43eee97d2995d9f9a4fea598a362e8d5"},{"version":"1.9.0","sha1":"4abb83116844af8c13a6df9df4cae1c9f1aefa54"},{"version":"1.10.0","sha1":"28416a4f7b0b2e188f02064a752e2cdb6423c851"}]},{"package":"sceneform-base","versions":[{"version":"1.0.0","sha1":"c962e2bed16cb216430eb9894abb3422c5e4f5e8"},{"version":"1.3.0","sha1":"ea26965ccf0d4f36dcfff0c6c9661cbd9ba85ef8"},{"version":"1.4.0","sha1":"25aba1a371274eb65cec54189dd0ed399c368ec4"},{"version":"1.5.0","sha1":"3f6d208e69a17e44de064d4958b9071ca81b08c6"},{"version":"1.5.1","sha1":"a32d3d24e9a22727b71a58677a5873ec8d58fbd2"},{"version":"1.6.0","sha1":"3867f10566fc02069873a6ddf2dcf5122b913f4f"},{"version":"1.7.0","sha1":"5db53f601be55a7461303693b43d5528ff973ec3"},{"version":"1.8.0","sha1":"93f82564179488b1fb381cd61558af5ba34e15c6"},{"version":"1.9.0","sha1":"cc2c06394e27f50b6fa0ec4ad0e06178de2d21da"},{"version":"1.10.0","sha1":"144b1db746ba7e43a46e7bbf2fa9249f9d551397"}]},{"package":"assets","versions":[{"version":"1.5.0","sha1":"b64174f41f6dae361bee7e487f1a011efefe9394"},{"version":"1.5.1","sha1":"e4d27d4e74a983477ec954c1ca135d64d81e9ce"},{"version":"1.6.0","sha1":"bcc23c619e1e5d64afbf8e49c22766a737a56e2a"},{"version":"1.7.0","sha1":"538bafeaea35e5c0e89ec0ff68ebfd25110fa456"},{"version":"1.8.0","sha1":"9fb851e5ee34178a341a54e381cc7a5c3e7cf729"},{"version":"1.9.0","sha1":"41258e7250c0fd072007ea2a6f7337b1696af4df"},{"version":"1.10.0","sha1":"26ee523b63c1aa0e98e5f8ea3ed62adb5f1afcc0"}]},{"package":"animation","versions":[{"version":"1.7.0","sha1":"6a78850c4654c9f92ce71b33fff0cbc54785ed10"},{"version":"1.8.0","sha1":"c0d9c5ed3077f7877c54f28e0a8a15944f5f080"},{"version":"1.9.0","sha1":"bf55f16001c7c13196886e6a9761c4ac57309e9"},{"version":"1.10.0","sha1":"5c2e4435af27e542571f521e87d2f2f21369cffe"}]}]},{"group":"com.google.ar.sceneform.ux","update_time":-1,"packages":[{"package":"sceneform-ux","versions":[{"version":"1.0.0","sha1":"ebbd0feecb54baf250de6cdea3809a714f845135"},{"version":"1.3.0","sha1":"9e7b93df2e317874a270645a03bc67c8c342546e"},{"version":"1.4.0","sha1":"9871679fbfa9f279b4b32fbde0241bbe0e4bc791"},{"version":"1.5.0","sha1":"94ebe26034f29aec69b5b11a17f24838d70970df"},{"version":"1.5.1","sha1":"ba7dacf265316ed14341bdbb60a0a36a389a6e4d"},{"version":"1.6.0","sha1":"e18b3c3b4a778f6475f921d7a771131231cff8a9"},{"version":"1.7.0","sha1":"24815b74a8d7b6853dcc5f5159d367090d4c8679"},{"version":"1.8.0","sha1":"3cb2b33c6fed2ec8a6472e28b5abd869fffb54c7"},{"version":"1.9.0","sha1":"b546316584bf6441ced7baf971e0b7e7b85779"},{"version":"1.10.0","sha1":"547d7d91a00ecd16e6092d910b8c5fc8d87e5493"}]}]},{"group":"androidx.test.ext","update_time":-1,"packages":[{"package":"truth","versions":[{"version":"1.0.0-alpha2","sha1":"f2acac1e9b9192f9ffe77e2cbf217451acf82f0d"},{"version":"1.0.0-alpha3","sha1":"f2b7f40ff3f887cc695f984540bc3b271bf3bcc9"},{"version":"1.0.0-alpha4","sha1":"bf16df3ddfdb1524a297a30ee8e39507a085efa9"},{"version":"1.0.0-beta01","sha1":"557a2fcabdbbe729efe816e70eee32d0f98c0f05"},{"version":"1.0.0-beta02","sha1":"fb536a56048df9590fa8a085ffff2dcf27f476ae"},{"version":"1.0.0","sha1":"d88fb09544b452a2a0bcf8444796727a102fdc02"},{"version":"1.1.0-alpha01","sha1":"19b0a2cf3435a78778c1215a4f399918f84b929d"},{"version":"1.1.0-beta01","sha1":"e51b7a008ce172f2382c24067a94154d3147e619"},{"version":"1.1.0","sha1":"ed9cdd3920ec1af044f6613682c4281c8b3be452"},{"version":"1.1.1-alpha01","sha1":"b2374c1c4356ee2a8e26e9bfa214989f1c37fbd3"},{"version":"1.2.0-alpha02","sha1":"6f475075d73a628d1c2b20517913d45b4a72a1ec"},{"version":"1.2.0-alpha03","sha1":"15a06006fe607ba58ec0e0a9d7e0b75226a43db9"},{"version":"1.2.0-alpha04","sha1":"c200bffdbe103e3e7fdc5c6a7fb3ec2996233d5"},{"version":"1.2.0-alpha05","sha1":"188c707346f10c76cb931760026f8fcc0b042a80"},{"version":"1.2.0-beta01","sha1":"30faec3d17e2ebc4a27e0bd6529f7a3d493b4d6d"},{"version":"1.2.0","sha1":"483e328e7a620aed7ada28a67c74b085f20412a0"},{"version":"1.3.0-alpha01","sha1":"4905be41aceedc3a8c9cd8e0a1539fdbd880a5e3"}]},{"package":"junit","versions":[{"version":"1.0.0-beta01","sha1":"c27bcd077e623996b0b6d84623b85d28b95f86cb"},{"version":"1.0.0-beta02","sha1":"d6d625a9c2e7afe2ae7a94a6c4aa430d88721747"},{"version":"1.0.0","sha1":"666296b76703c118ca6a66b4fb71ad2defdd9154"},{"version":"1.1.0-alpha01","sha1":"a4de71430c3a49078b1240d2dac6a3f94a2c880"},{"version":"1.1.0-beta01","sha1":"54ca32dfb179c990e44b6318c9e27ae7f43db684"},{"version":"1.1.0","sha1":"6560354f525b653db8b7f17b5dd75cf1248c7aad"},{"version":"1.1.1-alpha01","sha1":"361c8ee23e849c95e805ea5b337a661a9de7f057"},{"version":"1.1.1-alpha02","sha1":"76885616872988bed9c02fe4c728ccbdb0bd9b1e"},{"version":"1.1.1-alpha03","sha1":"4c5b1e86d6d3df74e17a6ff7116d4423712cc560"},{"version":"1.1.1-alpha04","sha1":"4d9d6d8bbe5cd201c78ccb74cf765f0993eebf20"},{"version":"1.1.1-alpha05","sha1":"1dd9da57a741e81459bdf7122300c57b9a55a742"},{"version":"1.1.1-beta01","sha1":"d8c39b8a79331fe3ada42b5993aad2180a4bf493"},{"version":"1.1.1","sha1":"883e4fbe94717b9381fd36d8f717f2865901408d"},{"version":"1.1.2-alpha01","sha1":"fd5b582cf4dbb2e85da4d605781ca202a34d4d1a"}]},{"package":"junit-ktx","versions":[{"version":"1.1.0-alpha01","sha1":"9ec8f7a52231ed3819f7ed33a46ba3f4eeaad5d3"},{"version":"1.1.0-beta01","sha1":"42ad78bbcd6b0653ef76f9b5f9f943e67e80cb47"},{"version":"1.1.0","sha1":"eab73a423ccf3e9f17163c81d6a18df71e53337c"},{"version":"1.1.1-alpha01","sha1":"9522e3a20def0859012efc860f2b234784b0becd"},{"version":"1.1.1-alpha02","sha1":"ae665563863af9fc50b7a3563ab333ffa7bff71b"},{"version":"1.1.1-alpha03","sha1":"ab178ada44099ff005dfae666b4df93fcad0dd67"},{"version":"1.1.1-alpha04","sha1":"a546712f79289797ab727b8b9f0076d01180c39d"},{"version":"1.1.1-alpha05","sha1":"1cf071303c4337b7ec8b69164ed0644432bad696"},{"version":"1.1.1-beta01","sha1":"704b5661a7c14663f2ae8f47376a20f3ccb2a66d"},{"version":"1.1.1","sha1":"6dd5a7d019c67ec1eaf4e3084bf1f3e6c1d18c59"},{"version":"1.1.2-alpha01","sha1":"10af22c475ff699ee4bd5b1c402e2284b52dfe7a"}]}]},{"group":"com.google.android.ads.consent","update_time":-1,"packages":[{"package":"consent-library","versions":[{"version":"1.0.0","sha1":"534280d329880c050c9a4118b3a3dce65e1c1888"},{"version":"1.0.1","sha1":"35a1c91446a142b734e7f3d75806e679123a46bb"},{"version":"1.0.2","sha1":"bc03396eab8b945103697a85d3e133a78f81d033"},{"version":"1.0.3","sha1":"7776fe3ad1cc15fdb1b78bf4edea7f5782ecccf1"},{"version":"1.0.4","sha1":"50f5595bf6584b5df408b530e7a103b0c174a664"},{"version":"1.0.5","sha1":"b8b394aab85aea1f483bfa7deab2d1a28fe29914"},{"version":"1.0.6","sha1":"878b0135e49bde033bab0f91b869f0fbd1e87da5"},{"version":"1.0.7","sha1":"7895157f17932eb541493ca9e9c57a5888c3c72a"}]}]},{"group":"androidx.versionedparcelable","update_time":-1,"packages":[{"package":"versionedparcelable","versions":[{"version":"1.0.0-alpha3","sha1":"78d22c90d6d788b139f138c964290922e8e1c17e"},{"version":"1.0.0-beta01","sha1":"cde06ce063c2fa73a3d6114bc887492cd44dc7f"},{"version":"1.0.0-rc01","sha1":"d61a54dde48aad5025ba187735a8027ee6738e0b"},{"version":"1.0.0-rc02","sha1":"5351b71778142e0c080c34db0cf375258e7bf563"},{"version":"1.0.0","sha1":"52718baf7e51ccba173b468a1034caba8140752e"},{"version":"1.1.0-alpha01","sha1":"23dfe577cde512417f48508f6355fd9b0f82801f"},{"version":"1.1.0-alpha02","sha1":"293c36016bbd26713982d9eda42214ccece4a62e"},{"version":"1.1.0-beta01","sha1":"823ca2e163ed7c9aac9c24fec0550b1542582bbe"},{"version":"1.1.0-rc01","sha1":"89b237116bff8661335573b5786ae14c722a91ad"}]}]},{"group":"androidx.media2","update_time":-1,"packages":[{"package":"media2","versions":[{"version":"1.0.0-alpha01","sha1":"dd4d8b594903a1900abf6a957b3956597b4af414"},{"version":"1.0.0-alpha02","sha1":"1e0f46cb36206502d8e951f20df6ad1fc964ecda"},{"version":"1.0.0-alpha03","sha1":"6a6a63d4eba79fbc33aa93309ae934811345ffb6"},{"version":"1.0.0-alpha04","sha1":"5cb362263358cf28b58b81df0d171c7ff0a90ab9"}]},{"package":"media2-exoplayer","versions":[{"version":"1.0.0-alpha01","sha1":"592ac61089d6686585fd0b0f9516aec2ef9c462d"},{"version":"1.0.0-beta01","sha1":"6e61266a1a4c7ac13f4052e1c550fb6c0f7539bc"},{"version":"1.0.0-beta02","sha1":"f79620619fdc033f649ef42358b86d86b8c72f36"},{"version":"1.0.0-rc01","sha1":"8c4e4b61333757a2bab7a444f10d9c4f7721c978"}]},{"package":"media2-player","versions":[{"version":"1.0.0-beta01","sha1":"778bede609ce8578114471bb27c3cbab6043564f"},{"version":"1.0.0-beta02","sha1":"19a1a5f01cd0712ed3c060370930502f22c7919b"},{"version":"1.0.0-rc01","sha1":"4458b8ac02a133e5e0faf626c7ede1c5336e1780"}]},{"package":"media2-common","versions":[{"version":"1.0.0-beta01","sha1":"94f76a141388536a08b0d78a2e4d1a20b438ddfa"},{"version":"1.0.0-beta02","sha1":"498602199d4091f6948260215c1738f13dc76cb3"},{"version":"1.0.0-rc01","sha1":"818ce6a838cca3d152dc9ba2279904511f4eedd1"}]},{"package":"media2-session","versions":[{"version":"1.0.0-beta01","sha1":"b58ab8e08fe760fb9a9971236ec8eca62f4f4795"},{"version":"1.0.0-beta02","sha1":"c3d6e7009d67e63f020eb1226ea48b43ee8abbeb"},{"version":"1.0.0-rc01","sha1":"3d2d58550d050c90c2fa1d8034c2535303c3049c"}]},{"package":"media2-widget","versions":[{"version":"1.0.0-beta01","sha1":"e6ec3f479c91cd99ed169486f2bd7929507e38a9"}]}]},{"group":"com.google.ads.afsn","update_time":-1,"packages":[{"package":"afs-native","versions":[{"version":"1.0.0","sha1":"53b5e0c44d3efd16ec92e84f0c0de2af4a9b3d2c"},{"version":"2.0.0","sha1":"b22c35e330d946200843f6f27f7f1cbd0c0bff5f"}]}]},{"group":"com.google.android.ads","update_time":-1,"packages":[{"package":"mediation-test-suite","versions":[{"version":"0.9.0","sha1":"af2060ec17cb64807166bb538f810531a827e4c1"},{"version":"0.9.1","sha1":"b667faa44a9e365ae9e840c480e499d547aa5d53"},{"version":"0.9.2","sha1":"9d143c72e9260f43b35b78b5a7aea2a43c771112"},{"version":"0.9.3","sha1":"5499a2606e7bc882245d6da1be3f229dc5520bae"},{"version":"0.9.4","sha1":"56f370c321d7cc6227a011ee8b6ca379ee86f049"},{"version":"0.9.5","sha1":"d012e3ad31c120973f14e7bffd309ba466689373"},{"version":"1.0.0","sha1":"f95294e62c55e5cacdb035aa411505e383a2991e"},{"version":"1.1.0","sha1":"a94b290fee654fe2dc98b212ccc7ca1ded00e757"},{"version":"1.1.1","sha1":"a434dbe06173cc881f5d763c755920ea2334e6ef"}]}]},{"group":"androidx.biometric","update_time":-1,"packages":[{"package":"biometric","versions":[{"version":"1.0.0-alpha01","sha1":"fe0be6b23892efd16a42c005c2ced06031a05c67"},{"version":"1.0.0-alpha02","sha1":"142036fa233f1cee2f91cdc1b08be38807662ae3"},{"version":"1.0.0-alpha03","sha1":"f433eaf3f7b83081d44d09b88ada884e0c58c6a3"},{"version":"1.0.0-alpha04","sha1":"e661a3898950444712669a3718cf96bbb82caad4"}]}]},{"group":"androidx.concurrent","update_time":-1,"packages":[{"package":"futures","versions":[{"version":"1.0.0-alpha01","sha1":"8f9431ccc63a212f509a281757e21877d37d1db6"}]},{"package":"concurrent-futures","versions":[{"version":"1.0.0-alpha02","sha1":"b4a4a1ada5a1ba3f8df9f9e6384a0a404ccc48cb"},{"version":"1.0.0-alpha03","sha1":"b528df95c7e2fefa2210c0c742bf3e491c1818ae"},{"version":"1.0.0-beta01","sha1":"355d6e1c54bf1e99dc4d4a9dfd851e7ce766c806"}]},{"package":"concurrent-listenablefuture-callback","versions":[{"version":"1.0.0-beta01","sha1":"cf079e061ed320bfcac78c2ba4075d72167fa1ec"}]},{"package":"concurrent-listenablefuture","versions":[{"version":"1.0.0-beta01","sha1":"fa16925dddc6ddec5daadf125f13527e4d2a50af"}]}]},{"group":"androidx.activity","update_time":-1,"packages":[{"package":"activity-ktx","versions":[{"version":"1.0.0-alpha01","sha1":"cc0b00fe25b3359e447e3370e73881ba6b2fe167"},{"version":"1.0.0-alpha02","sha1":"67af527b556bcd548d2cc8cb9c133a9c9c3515e"},{"version":"1.0.0-alpha03","sha1":"b5c906df461af81535cf91272f4785657077f6a9"},{"version":"1.0.0-alpha04","sha1":"9acae2061e010789f18b2f8f12aee8fd9189cf6"},{"version":"1.0.0-alpha05","sha1":"d767606644212da02a96d681bb6b6619eaa52adf"},{"version":"1.0.0-alpha06","sha1":"b1a0ce3ad89951ae16c3985501620ce6d960f2cd"},{"version":"1.0.0-alpha07","sha1":"4a062a168689e9f475694730356d8a4c3c4e1644"},{"version":"1.0.0-alpha08","sha1":"f01d6cb1595a437199923bb79f3429436344f095"},{"version":"1.0.0-beta01","sha1":"f3b8fe45e97fbc1624327b05a719d70e7825fa0c"},{"version":"1.0.0-rc01","sha1":"5fa8e124dc37b4fd806002fde762fda62f4f7c6"},{"version":"1.1.0-alpha01","sha1":"adf0fa45227fa4f951ac4efe5b22451e93f30cb5"}]},{"package":"activity","versions":[{"version":"1.0.0-alpha01","sha1":"1816262b7956884935139db7b6fe2d6f2213d9ce"},{"version":"1.0.0-alpha02","sha1":"cae846ef58c57c2e05aebe82d8ac217822c635c8"},{"version":"1.0.0-alpha03","sha1":"5ed337743cef8defe0e46819673f2eb681b065bd"},{"version":"1.0.0-alpha04","sha1":"22b76874fb0d414541fb566a0e5d7e472fc364a"},{"version":"1.0.0-alpha05","sha1":"d2b7c11bf851aeb760fb9ad25864fcf98247ba99"},{"version":"1.0.0-alpha06","sha1":"c02ad5e7fb84f3cbe18c068dd28402559bc816ce"},{"version":"1.0.0-alpha07","sha1":"f2df52a963636886de3f5a8fe52696881e0e09ef"},{"version":"1.0.0-alpha08","sha1":"616bffe618c834b12e43b60c9eaf2a96ce1b7fb5"},{"version":"1.0.0-beta01","sha1":"4b91726aaf4d6cce306839b9282bb6c037e84a32"},{"version":"1.0.0-rc01","sha1":"3339ce77cea7349781e82681a532b671ea7dde59"},{"version":"1.1.0-alpha01","sha1":"25e485fa6f09e624731eb19d60ff450fc393fa72"}]}]},{"group":"com.android.tools.apkparser","update_time":-1,"packages":[{"package":"apkanalyzer","versions":[{"version":"26.3.0","sha1":"fec3ab7fc075fb45cbf46c6be012f6ba91babf2"},{"version":"26.3.1","sha1":"66f18b322ad8c7617a57b82499491ce5013b794f"},{"version":"26.3.2","sha1":"72a343bfaa8b8ab0ee94bcaabba25ab213b4c1f8"},{"version":"26.4.0-alpha06","sha1":"816d17d8769faea7c2543b18caf5d4b7d228bfc5"},{"version":"26.4.0-alpha07","sha1":"e3a0a4c75568306b7cd03e6793e60c0c9abbfeaf"},{"version":"26.4.0-alpha08","sha1":"af4f197ace63cf3c98375757b37a5a5fda4571e7"},{"version":"26.4.0-alpha09","sha1":"3d3451f2f0d5923cc5aab3a72de35d28e52f22c"},{"version":"26.4.0-alpha10","sha1":"dbce99824ead225ff7208d11ac677057ffc9e4bc"},{"version":"26.4.0-beta01","sha1":"95961b884103f9d426141f23736e107c4def3520"},{"version":"26.4.0-beta02","sha1":"a723e24d044754cd00feeb5c8a8b0ffd23c83a94"},{"version":"26.4.0-beta03","sha1":"726d3845fd311b8ba83235178ff3d88972b7cf35"},{"version":"26.4.0-beta04","sha1":"82c8d9f83805dadbaa609552932094243f334432"},{"version":"26.4.0-beta05","sha1":"f0eb06a16cd8cd16835211761c9b12d83b11dc88"},{"version":"26.4.0-rc01","sha1":"af3e354f42a39136f59d9ff86a349893007492cd"},{"version":"26.4.0-rc02","sha1":"87895055703fcb7734de4f77d1cfe164d4acb802"},{"version":"26.4.0-rc03","sha1":"2256d86d8ffe0108391333dcdcd2b1a2651e834e"},{"version":"26.4.0","sha1":"85549b9ee696f32febba9c4b19ffa14fae2ecdb7"},{"version":"26.4.1","sha1":"adece087801bbb8e46a15d4141a6c9008bf9e5cb"},{"version":"26.4.2","sha1":"5c5e00f77efaebda21e073e41c58617f3e768ad5"},{"version":"26.5.0-alpha01","sha1":"3cb91e261e813ab8e5568ab21cb7e342c4708412"},{"version":"26.5.0-alpha02","sha1":"ce0c072c0a43c0f7b299fe173baa8da82b9b6750"},{"version":"26.5.0-alpha03","sha1":"410eba91fb488bdbad6fe2f9c3b611bdea273209"},{"version":"26.5.0-alpha04","sha1":"8141fb7dfc5a94d7126cfa048702481fd73612cc"},{"version":"26.5.0-alpha05","sha1":"cc982e6a0d41de8f7590db8766a0bd19d3e9ddfb"},{"version":"26.5.0-alpha06","sha1":"d6a675364dd694f57c5fabde86bac831388f27fc"},{"version":"26.5.0-alpha07","sha1":"cb914ebcb90242dd8d62e9d593576f5a7ad17ef6"},{"version":"26.5.0-alpha08","sha1":"a1468d7db045956372c861a49d5c6971db217de3"},{"version":"26.5.0-alpha09","sha1":"4275ebcfadd5e82eabddf1c250376d5a134c9cd8"},{"version":"26.5.0-alpha10","sha1":"151049b32719a6823fd1fc711d60780d4c21df74"},{"version":"26.5.0-alpha11","sha1":"7141f2bd87a52535de77f29df750720448fc39fb"},{"version":"26.5.0-alpha12","sha1":"abf4a0b120fdd92c3eb19cba1ae8eb942d59f52d"},{"version":"26.5.0-alpha13","sha1":"2f7c59ce58cfe809dac885cd86f1ada69580b5d9"},{"version":"26.5.0-beta01","sha1":"964fa3ed97274a4591dbea915e1f503c96592744"},{"version":"26.5.0-beta02","sha1":"b08749b1276422448c31455c9813f2ec5702df16"},{"version":"26.5.0-beta03","sha1":"322851a1dae22ab56931db369813f9064548fcc9"},{"version":"26.5.0-beta04","sha1":"e18d39895333904a67d743d4a5c2858e2fb2a253"},{"version":"26.5.0-beta05","sha1":"fbfc22b610975289c2f099ba91ba2c84ca3e2385"},{"version":"26.6.0-alpha01","sha1":"8d7f8f893c513b22397ebecd99aa11a6b428e871"},{"version":"26.6.0-alpha02","sha1":"bb18f9848450ee9b578a986a9b78190ee7213499"},{"version":"26.6.0-alpha03","sha1":"c77c0a090144f9e29e5e51b9c3f384a9b69a0095"},{"version":"26.6.0-alpha04","sha1":"7ff8a2a8cb2f4b01e03af15e596be06513341077"}]},{"package":"binary-resources","versions":[{"version":"26.3.0","sha1":"4e4ec7c20bc8646f7c95412f894617530834119f"},{"version":"26.3.1","sha1":"c7c8c32eef34c188608bb47ee3a4ed49527b3c5"},{"version":"26.3.2","sha1":"71c1396f5e0c74a07d4ef5cc948616448b535adb"},{"version":"26.4.0-alpha06","sha1":"33c65043bd4cab734b07db9f5155ef7b4fc8f322"},{"version":"26.4.0-alpha07","sha1":"df0c40d70b3de7a1e32053a9ae89df37af1434dd"},{"version":"26.4.0-alpha08","sha1":"e0e21ce3ceb66df00df2bea8616a860953de77af"},{"version":"26.4.0-alpha09","sha1":"2de862b02d5df4646a07aa306855706efb31cf3c"},{"version":"26.4.0-alpha10","sha1":"3e9ad0bcd9b15c77e79be860d2fe1f32cf3d75cb"},{"version":"26.4.0-beta01","sha1":"dbdad8196c26d7d4fe50c98162c5155a1422fc80"},{"version":"26.4.0-beta02","sha1":"a6cafe88eacef327aa14610f28917b1b91baeb9e"},{"version":"26.4.0-beta03","sha1":"356372c6547c74f567eb6213bf6726e80a19feaa"},{"version":"26.4.0-beta04","sha1":"61a565914bf9dcff140e30d55974e77b3174ece7"},{"version":"26.4.0-beta05","sha1":"af2cd679c32f801a7bdeaf9303e7961385aa10fa"},{"version":"26.4.0-rc01","sha1":"392e4b0e60277487d4c847fd3165f36f544f1b2a"},{"version":"26.4.0-rc02","sha1":"396b9bbd74e10f63b836e97b2b30a38fce598981"},{"version":"26.4.0-rc03","sha1":"2573c34392b22a39fcc8f9a9d67684ec323b8775"},{"version":"26.4.0","sha1":"2901a777f4cb8bc7966e7c34d1eca05f53d2b3fd"},{"version":"26.4.1","sha1":"b1c3402c0d82d4cf8be500fbace54cc17bfb0d0f"},{"version":"26.4.2","sha1":"e51fef86d31808adbb5671b9e37de98fc7b54ef5"},{"version":"26.5.0-alpha01","sha1":"cfb0cadb6c1f9d86e4c6c8511c617f5df7fb347f"},{"version":"26.5.0-alpha02","sha1":"90341c610889ac9bbc524329c17b79b0dc663a08"},{"version":"26.5.0-alpha03","sha1":"3e791b499b650687a324b6911be782ddfabdc98b"},{"version":"26.5.0-alpha04","sha1":"9932f2dfeb46c9a7991f2bfc413aa75f018ce3cb"},{"version":"26.5.0-alpha05","sha1":"787622be882785b3be9a6d42a51c9fe89ead0e5f"},{"version":"26.5.0-alpha06","sha1":"5272ac1c679da26b3780a0a6c923d64fa7b846b3"},{"version":"26.5.0-alpha07","sha1":"dea60bf6c5f75ca84c23c80aa0f4cd08b2efb571"},{"version":"26.5.0-alpha08","sha1":"323a64ddc7cdb5bc28735ef4540752031e71df4a"},{"version":"26.5.0-alpha09","sha1":"2eaabe30b48721e9cb853ac691996c8550f5b9cc"},{"version":"26.5.0-alpha10","sha1":"d7c77735322038c2a914b36d404248a2706062f2"},{"version":"26.5.0-alpha11","sha1":"6d78902f68f84ec325a5ac88f8f72d6a58e83c0b"},{"version":"26.5.0-alpha12","sha1":"b4b1720a2f80554541f7b5e0740672099899c980"},{"version":"26.5.0-alpha13","sha1":"252e863fd7cc7a59ea5c220ca0e4c48b0759969a"},{"version":"26.5.0-beta01","sha1":"25f4d202f7c8e0edf4ee57678cb931ddcf4c077c"},{"version":"26.5.0-beta02","sha1":"581956e8b14a3afd0a1dbc26195b880bd74555ab"},{"version":"26.5.0-beta03","sha1":"c514f82860b2e44365d04403eb0cc21a2dd79c31"},{"version":"26.5.0-beta04","sha1":"b589f1cdc296fe4adb6cd0ac20e520c182631524"},{"version":"26.5.0-beta05","sha1":"b7d76b72c306a5e5f38f9f84237cb2e8f7a669d5"},{"version":"26.6.0-alpha01","sha1":"d9b484f488b63a207e07ed2ddf5cfdc376f4f200"},{"version":"26.6.0-alpha02","sha1":"ac1b27e805a0f5b51925560837f47f1a34a5fca6"},{"version":"26.6.0-alpha03","sha1":"3b0eb20d9c05e9852bc5c6e686581f5a80909d47"},{"version":"26.6.0-alpha04","sha1":"5f0113dbcf9a188fe1f8d95c4b64a01ac8838359"}]}]},{"group":"com.android.tools.pixelprobe","update_time":-1,"packages":[{"package":"pixelprobe","versions":[{"version":"26.3.0","sha1":"10d061103d536afa9766e96609dae72d318f07db"},{"version":"26.3.1","sha1":"50afe8a835c32c93baa8ea6b943f31536f6f0081"},{"version":"26.3.2","sha1":"406588b7dd468b284a860f8b4d697e839ccefa9d"},{"version":"26.4.0-alpha06","sha1":"26ec01a87595d658f866a96af13ad7e6e9a563bf"},{"version":"26.4.0-alpha07","sha1":"5696d081955ac4809df98a9ab2963bc663a6e221"},{"version":"26.4.0-alpha08","sha1":"d205ded266796ee0825d5b19904222e071a44575"},{"version":"26.4.0-alpha09","sha1":"c5f8a03a08b19a1891a688b84703df160ad9f7f7"},{"version":"26.4.0-alpha10","sha1":"c426765dc239e87f2a57be9e5a5f180732c64636"},{"version":"26.4.0-beta01","sha1":"bafc1b26e75d223bbf3b1164485b8919fc71ea2d"},{"version":"26.4.0-beta02","sha1":"6b39d82747ed43dafbda0944f73640f373351e2e"},{"version":"26.4.0-beta03","sha1":"79282e2a35cf3af90a3b6556de23907ddce151d6"},{"version":"26.4.0-beta04","sha1":"490e52b061f4f0d4f7d11ffaf97da98d842ca8e7"},{"version":"26.4.0-beta05","sha1":"1d188878d7376a5e4fb68b7d4e003f1f6c1d9e5d"},{"version":"26.4.0-rc01","sha1":"57d4e844baa234e84f17422fbda6b89904d6706d"},{"version":"26.4.0-rc02","sha1":"bba69eba9a561d5a8f19aefb4958a95a25c51948"},{"version":"26.4.0-rc03","sha1":"e8fc8b7e6b4a31bc53998b2ec6f9266442b0300"},{"version":"26.4.0","sha1":"afd45c6b5bc163f76f5f64e381c08d4d8c56a8a5"},{"version":"26.4.1","sha1":"571a98db54b1fffc65c36f5d637a485439cc554b"},{"version":"26.4.2","sha1":"69f4dd5281dccd6d3361ed1599db249256b7afed"},{"version":"26.5.0-alpha01","sha1":"cb7ccdfde125f69ff37edcf3a84102582a00bfcb"},{"version":"26.5.0-alpha02","sha1":"7c2d50b041fda53787f0d61d85e0a468acf8a429"},{"version":"26.5.0-alpha03","sha1":"287805f46a2480332ba360235a0b24ab6a1941d8"},{"version":"26.5.0-alpha04","sha1":"c2e6022d436141223182b9618a49632ce759aae3"},{"version":"26.5.0-alpha05","sha1":"6366158f65d1c820a66d2ca9d0a6cf2cff695129"},{"version":"26.5.0-alpha06","sha1":"eba9efb4a940eb2c2b35a5996db1549a452b80a"},{"version":"26.5.0-alpha07","sha1":"75393b9ee7cb56044827b0b960b1db1e9845ee3e"},{"version":"26.5.0-alpha08","sha1":"a14c53ab2777d90b647354c206922b937a6e1136"},{"version":"26.5.0-alpha09","sha1":"34c2ec1848e4e31c68e892f5d12f45ddf83d839a"},{"version":"26.5.0-alpha10","sha1":"c6ba04a5e75f767703fbb591d3c8772414717ff3"},{"version":"26.5.0-alpha11","sha1":"38e7f917e62f3686aee2e3bef831bde51ced4d7b"},{"version":"26.5.0-alpha12","sha1":"7d42edc0fd0065cd37779935687e97528bc05ab5"},{"version":"26.5.0-alpha13","sha1":"491a7f2921a65d5d92c9363760210da46406c44c"},{"version":"26.5.0-beta01","sha1":"2cb6a18110b51b2a6e4aa1c7417130ac0fcc1588"},{"version":"26.5.0-beta02","sha1":"7e3bc96c0aeb82e986750167cd2cd28267621beb"},{"version":"26.5.0-beta03","sha1":"518924068f43618968740b92a13e46e339bf85eb"},{"version":"26.5.0-beta04","sha1":"2f22ea770d6fc33807fb3250ebea43351dc150a8"},{"version":"26.5.0-beta05","sha1":"2e2bfac2c1ea6cadad73249bbea9cb89bd789154"},{"version":"26.6.0-alpha01","sha1":"952a48af708148fa0a8b0c3fe7a5ca64da124c71"},{"version":"26.6.0-alpha02","sha1":"3f7f8503c4130f21f674d030330d071530965ebe"},{"version":"26.6.0-alpha03","sha1":"78211ae87d765f6042f33027eb6a5012d66a82a"},{"version":"26.6.0-alpha04","sha1":"2d8d8d49cd8280f6f0001576480479259d853ff0"}]}]},{"group":"androidx.textclassifier","update_time":-1,"packages":[{"package":"textclassifier","versions":[{"version":"1.0.0-alpha01","sha1":"2d62096728055912779b996153e0d98df534d7aa"},{"version":"1.0.0-alpha02","sha1":"ea1da86fabef76edb2bef60f8154d3131fd72ce"}]}]},{"group":"androidx.remotecallback","update_time":-1,"packages":[{"package":"remotecallback","versions":[{"version":"1.0.0-alpha01","sha1":"ac6b2cf400260acb1eb3fe5e62b148c7cf2db09c"},{"version":"1.0.0-alpha02","sha1":"b6574e74210ed70edbdc26fc5f0579fb017ec22d"}]},{"package":"remotecallback-processor","versions":[{"version":"1.0.0-alpha01","sha1":"63fa4af3640ab5580915ff2246d489f3a9121f27"},{"version":"1.0.0-alpha02","sha1":"63fa4af3640ab5580915ff2246d489f3a9121f27"}]}]},{"group":"com.android.tools.chunkio","update_time":-1,"packages":[{"package":"chunkio","versions":[{"version":"26.3.0","sha1":"f5cc67cadcbc8b928e95ab288a7717d4da9fac2e"},{"version":"26.3.1","sha1":"9a58005edaf8940333fe420401e9141a697f1f6"},{"version":"26.3.2","sha1":"27783e53e38b4361c31ea97c692b4018eb0beeb3"},{"version":"26.4.0-alpha08","sha1":"5beb9a7871b7e62bb683e8478f349cd4bc73f00"},{"version":"26.4.0-alpha09","sha1":"8083c8e73417e98403f78f1f8ebbc7be02692d57"},{"version":"26.4.0-alpha10","sha1":"eb0ccc577f768d2bb328dc5fb905dec6c4a40a91"},{"version":"26.4.0-beta01","sha1":"d045b6b1590730861b4380a64a7b7ae28fef671"},{"version":"26.4.0-beta02","sha1":"5f1cf68bfe1c65c04cfbb6283254dc9e431f5905"},{"version":"26.4.0-beta03","sha1":"41833677d79a971f05a4a62e85ed30e3429d5364"},{"version":"26.4.0-beta04","sha1":"61f88dea82ab5b8ca0507b5a708e0a541df981ef"},{"version":"26.4.0-beta05","sha1":"bfdffb2bdf829690d7c19f4a23f2e2af7806c175"},{"version":"26.4.0-rc01","sha1":"d002e97c361f3a3026973159fdc0e9e6cd9db776"},{"version":"26.4.0-rc02","sha1":"616198bf91a372fb8635e6f74ca9e3820e685e6b"},{"version":"26.4.0-rc03","sha1":"6381b077d17fb2ed6f24fb669be0fb7d296bfcde"},{"version":"26.4.0","sha1":"f7f10258a6d5f89c7f68e13ab3c03c6a3e21a2b"},{"version":"26.4.1","sha1":"3974bbd47f21a35738943146500180fa0e60d716"},{"version":"26.4.2","sha1":"4493fa1682455d6451a2ab557d0eae19d49ce44"},{"version":"26.5.0-alpha01","sha1":"4c8047dc09ea2564d6c7d76f3205d992556677ad"},{"version":"26.5.0-alpha02","sha1":"d57f087cd1c5bdb198f288e033747574d47bc818"},{"version":"26.5.0-alpha03","sha1":"796e3c5f709a81e34013e2de75812101b5e49a91"},{"version":"26.5.0-alpha04","sha1":"aef706a120202e9cd09f6b15d7f7ab2a9388c4f"},{"version":"26.5.0-alpha05","sha1":"838126774c7696eb171b337f31107f09c87ea073"},{"version":"26.5.0-alpha06","sha1":"388072511f177974c3b6ec31aa47afab73de050b"},{"version":"26.5.0-alpha07","sha1":"ff9979c11fcdcddf5327a1772200e8fdb72f423b"},{"version":"26.5.0-alpha08","sha1":"e8c3564bb6e8c1ad9185b6ab5080883bbea26dce"},{"version":"26.5.0-alpha09","sha1":"ecd5b95632158e36f94527b1ff93d36ea261803f"},{"version":"26.5.0-alpha10","sha1":"992e51917ad82f1650fb7e68f24a19627d6c37f4"},{"version":"26.5.0-alpha11","sha1":"1c55d5714169ad204a274a079c2c390a190e59d2"},{"version":"26.5.0-alpha12","sha1":"51b0a1eb5bdee333edc96b1c2f5a8ca5f0f23d06"},{"version":"26.5.0-alpha13","sha1":"bbd586c886cbe96c982094d69e1f5d98f4fc3311"},{"version":"26.5.0-beta01","sha1":"dd6fb29880c1ddcff2d90832a5fef3aee2f0e3c9"},{"version":"26.5.0-beta02","sha1":"22217d51d6812ee4788091bad810c5463f18b75c"},{"version":"26.5.0-beta03","sha1":"a50011e6b666f072867d49747b1e5d4e4121201a"},{"version":"26.5.0-beta04","sha1":"1acb42deec82496776a07ab8652db768fc6bb2b9"},{"version":"26.5.0-beta05","sha1":"284b5f48364c313e504c53a0316429936ebef8f5"},{"version":"26.6.0-alpha01","sha1":"609e45c7bca4ab0730cab68a98260200374d6e4b"},{"version":"26.6.0-alpha02","sha1":"5621dd8bca19230afa98a1bdf0b9a49ebb7c19a3"},{"version":"26.6.0-alpha03","sha1":"265dcdf35c65edcd352f1e34fa4961db4da1106a"},{"version":"26.6.0-alpha04","sha1":"3d636f03a997be8f8dff44dce1210e7f1114ee8c"}]}]},{"group":"com.android.tools.fakeadbserver","update_time":-1,"packages":[{"package":"fakeadbserver","versions":[{"version":"26.3.0","sha1":"cd1780359564806f25a1acbd864a1e56a6fb6ad5"},{"version":"26.3.1","sha1":"44a227bfae6950ba09838ee07d23bfba17f66ddd"},{"version":"26.3.2","sha1":"d6a38705d81ea025a06db6204029a5472e06a6a4"},{"version":"26.4.0-alpha08","sha1":"200a48bc6e8f76a479f92c8c77944487896f8aa9"},{"version":"26.4.0-alpha09","sha1":"b4aaafbdb3e9d7d4933f16057a583508b7db048d"},{"version":"26.4.0-alpha10","sha1":"293b159a71f5d1094399d88901d77ca7cb65aab8"},{"version":"26.4.0-beta01","sha1":"406318071a44d9f5382d720c409a48c9024fb7bd"},{"version":"26.4.0-beta02","sha1":"c6a9263be5f3d892177ac34290465248f120de21"},{"version":"26.4.0-beta03","sha1":"e02b82c293a22ad5a94cc886da4b1544545a64dd"},{"version":"26.4.0-beta04","sha1":"7ff55b48e12acb23a9df2fcccff7f0427c8642cb"},{"version":"26.4.0-beta05","sha1":"c3940bd9a95f4bf7cd768b752e2fe02f3ccd2dd1"},{"version":"26.4.0-rc01","sha1":"8f33a744a2484cb80a869a1bcbd4eba016e90b42"},{"version":"26.4.0-rc02","sha1":"27b560c32f2dc888e7044252ddc80555463b61d7"},{"version":"26.4.0-rc03","sha1":"c1f2037b18992ad8a42b2a10470400523fb161dd"},{"version":"26.4.0","sha1":"4941684023a394e7fe034c9a0254db456aee543b"},{"version":"26.4.1","sha1":"3c25b7ec6a079b27b4775a462c3b73d61e2820f2"},{"version":"26.4.2","sha1":"328b3add68c4cd53ccc3a8557391dc4a283be96c"},{"version":"26.5.0-alpha01","sha1":"cf1672525ef54a8f857a6d8aed5d1687391b5e80"},{"version":"26.5.0-alpha02","sha1":"477a55a65c3f1652576dc2c5bd08205cd7c3b688"},{"version":"26.5.0-alpha03","sha1":"3d8c836778496d1b3e25c908e1769f6b7fee5e39"},{"version":"26.5.0-alpha04","sha1":"c9c6e727ddc17c8f8bcf5e65baebc4effe895df4"},{"version":"26.5.0-alpha05","sha1":"ec9865f581def0384bf989f488b426703e8463ff"},{"version":"26.5.0-alpha06","sha1":"c9cf0cc544837283da0dbbeef0ad95ca1b6a4985"},{"version":"26.5.0-alpha07","sha1":"6d72de3b724c6c0332b6420d9ed0dcb66a991943"},{"version":"26.5.0-alpha08","sha1":"e180820e5fbe70803805dd96d24ce28d140a03cb"},{"version":"26.5.0-alpha09","sha1":"380aae9a99a86b818a3a235c26306847bb2d6c7e"},{"version":"26.5.0-alpha10","sha1":"c61d4063036444699531928dbcf1386494936a0c"},{"version":"26.5.0-alpha11","sha1":"35f18404a5a17a0a11724e325e4d0355ed456eda"},{"version":"26.5.0-alpha12","sha1":"63729af47180e6cbdc46ebc134c8fbc2484df6ce"},{"version":"26.5.0-alpha13","sha1":"e61e670327e722ad036fab9c053fe2e07a2289e5"},{"version":"26.5.0-beta01","sha1":"2391848f0d567aec033ce191b374e97ab49e489d"},{"version":"26.5.0-beta02","sha1":"dc9193c466cfde37709cf71c00041469c377c01d"},{"version":"26.5.0-beta03","sha1":"d6bccde44c661ea741ef2deff5e723baf40c7c56"},{"version":"26.5.0-beta04","sha1":"4260edb198cd903e8f605c9037c33cac8b459689"},{"version":"26.5.0-beta05","sha1":"18c73613018db9fc7974056941aa6334ecb82eae"},{"version":"26.6.0-alpha01","sha1":"3c55435729d1a403a9599b9a0e6459ca5e6b3559"},{"version":"26.6.0-alpha02","sha1":"8cf4086c2cf342864ced30f73cf7592680bbce96"},{"version":"26.6.0-alpha03","sha1":"f612b1fd1cd042c86415bdf45871b5cae3229d9c"},{"version":"26.6.0-alpha04","sha1":"59244ec2d6b70b72b6d16730631fc3405dc76141"}]}]},{"group":"androidx.savedstate","update_time":-1,"packages":[{"package":"savedstate-common","versions":[{"version":"1.0.0-alpha01","sha1":"9144496fb7c11280ef621854908a17c5a83f8dc5"}]},{"package":"savedstate-bundle","versions":[{"version":"1.0.0-alpha01","sha1":"9ccd58d648fd3b5ab5cb72fc4ded9b83602a6f2e"}]},{"package":"savedstate","versions":[{"version":"1.0.0-alpha02","sha1":"bb4e4b7444f0714d2289f8bbe1f0d70ae5711eee"},{"version":"1.0.0-beta01","sha1":"f766db07fc88cb77aadd89f0bad7b3a5f551f566"},{"version":"1.0.0-rc01","sha1":"8b8f9e98e359be8d0466c15a682ba246100e3edc"}]}]},{"group":"com.google.android.libraries.places","update_time":-1,"packages":[{"package":"places","versions":[{"version":"1.0.0","sha1":"8b00a9086dce4285ad057967c2f4a15b574f57e3"},{"version":"1.1.0","sha1":"92b674d52a7daa44c96efd2cd11064903cefc39c"}]},{"package":"places-compat","versions":[{"version":"1.0.0","sha1":"a2841c53868b64b33976ae27cc3fc4e7591cfd50"},{"version":"1.1.0","sha1":"d2c645fb2097f4780c1541d7f175fa715a201d2"}]}]},{"group":"androidx.viewpager2","update_time":-1,"packages":[{"package":"viewpager2","versions":[{"version":"1.0.0-alpha01","sha1":"f2e1e4d5d9dfc4a50ba2616c2ace90447060d7fe"},{"version":"1.0.0-alpha02","sha1":"345a627723406575047e3fa960ed522e92b1f17e"},{"version":"1.0.0-alpha03","sha1":"c0d18b2432238e7c9e56fe86e88d49fedaec2789"},{"version":"1.0.0-alpha04","sha1":"9e7d592bade391109ed517d7db96bd61ee7118b8"},{"version":"1.0.0-alpha05","sha1":"c39758fc60b35933a13d6b4c2ddc14815667831c"},{"version":"1.0.0-alpha06","sha1":"5935a8eb8b314f212e01f3d3f1d145854f00e63f"}]}]},{"group":"androidx.navigation","update_time":-1,"packages":[{"package":"navigation-fragment-ktx","versions":[{"version":"2.0.0-rc02","sha1":"1a110be890d5b1eed8bc8e75df69765b24435403"},{"version":"2.0.0","sha1":"991e263776320cf3d09fd37aa0af03ff73be45a5"},{"version":"2.1.0-alpha01","sha1":"c52782893daf89104042f0a832ccbb843de2b4b9"},{"version":"2.1.0-alpha02","sha1":"dcce841b82790b01f32dfdaae87e70d1b6640636"},{"version":"2.1.0-alpha03","sha1":"624c5ead2b925023016fe9e5c0b0b98f79e17f7a"},{"version":"2.1.0-alpha04","sha1":"d62541e5c0e72e514a6090baff9bb93a80ee1888"},{"version":"2.1.0-alpha05","sha1":"31a3f3b3bae955429e9d110d5bf821e8022040c9"},{"version":"2.1.0-alpha06","sha1":"20171781f3d5202ce9a1eec2e95dd5a99ce32c25"}]},{"package":"navigation-safe-args-generator","versions":[{"version":"2.0.0-rc02","sha1":"b673b641a07ac12f7c6d1921fa64f28ce2432a40"},{"version":"2.0.0","sha1":"b673b641a07ac12f7c6d1921fa64f28ce2432a40"},{"version":"2.1.0-alpha01","sha1":"9438ae6fce886b774a6997544b850b8ed0715c88"},{"version":"2.1.0-alpha02","sha1":"9438ae6fce886b774a6997544b850b8ed0715c88"},{"version":"2.1.0-alpha03","sha1":"87733a1d38c4c89c78490d7a72f4a27aff5dae9d"},{"version":"2.1.0-alpha04","sha1":"68a881b759036c2df467303a00fc931625878290"},{"version":"2.1.0-alpha05","sha1":"68a881b759036c2df467303a00fc931625878290"},{"version":"2.1.0-alpha06","sha1":"3e842d21453e36acfa38534c827b3c6fce407058"}]},{"package":"navigation-ui","versions":[{"version":"2.0.0-rc02","sha1":"a4df4f4db53ed6a04a3276dd75aca5f3a07b25c2"},{"version":"2.0.0","sha1":"ab1b154e12a803aecc748c0cea8ee59d55672154"},{"version":"2.1.0-alpha01","sha1":"94accb6cda81969668009e1e16ec77ba01f082ee"},{"version":"2.1.0-alpha02","sha1":"72279aaf9e5817984639862632a0161473eaafe9"},{"version":"2.1.0-alpha03","sha1":"6cddca5a17d2d210e7ddd7c83fc61cc577a098aa"},{"version":"2.1.0-alpha04","sha1":"83b70e97a74ca58313bc6a2f59e817d95147b982"},{"version":"2.1.0-alpha05","sha1":"6ef8813c17bf30e31d44e7b786a8084b6016d239"},{"version":"2.1.0-alpha06","sha1":"40a2341ac6b633b9c384e42b9c328a07568c17e4"}]},{"package":"navigation-fragment","versions":[{"version":"2.0.0-rc02","sha1":"11f16eef6cc29924bddf70ad7a2ffc82875e62a6"},{"version":"2.0.0","sha1":"a7f188e38b8a57dd895b5422aaa3b9b93211ff9b"},{"version":"2.1.0-alpha01","sha1":"e77b15055c5be086bd0e4cb5d4dcd4c18c6dfcfa"},{"version":"2.1.0-alpha02","sha1":"99f00807eee8dc5e5f2eadb62ec0de7cd9b58a7f"},{"version":"2.1.0-alpha03","sha1":"9de2b102f6c6827ae347cd00b506a2a8c70eb131"},{"version":"2.1.0-alpha04","sha1":"ef5a305ce0308428fc1acdc7755a8e18601f901f"},{"version":"2.1.0-alpha05","sha1":"e4dd06f56ea43e750051a9cf6402d1b40bfa2d73"},{"version":"2.1.0-alpha06","sha1":"e5fdf93dca541bf3321ae58a89ab688221bd46da"}]},{"package":"navigation-safe-args-gradle-plugin","versions":[{"version":"2.0.0-rc02","sha1":"d93038703a4ed08cdb64501130b44e5823f15a12"},{"version":"2.0.0","sha1":"d93038703a4ed08cdb64501130b44e5823f15a12"},{"version":"2.1.0-alpha01","sha1":"d93038703a4ed08cdb64501130b44e5823f15a12"},{"version":"2.1.0-alpha02","sha1":"d93038703a4ed08cdb64501130b44e5823f15a12"},{"version":"2.1.0-alpha03","sha1":"e96a1b4979fe140abb7e1f41bbe3f0ad07e77bc"},{"version":"2.1.0-alpha04","sha1":"cb2068a88af759c5521c1c53429adaca2fb2c224"},{"version":"2.1.0-alpha05","sha1":"a18df7ba5238a89e4fd65ed1c883fcadc1003fa8"},{"version":"2.1.0-alpha06","sha1":"fd29fd816eb17503a1e4544cc342cd4879aa8e37"}]},{"package":"navigation-ui-ktx","versions":[{"version":"2.0.0-rc02","sha1":"bd9c934f5d5b4d18bdb67b4a4477bef5e8a3ef71"},{"version":"2.0.0","sha1":"813a051efa260e14099fa7dcdc1fcec5cde748ca"},{"version":"2.1.0-alpha01","sha1":"b63091a590a9b7015f17dfdd3d0594eba1e79ea6"},{"version":"2.1.0-alpha02","sha1":"9a6763f1944072ff10e76ac1909b2866eecb0fcf"},{"version":"2.1.0-alpha03","sha1":"450b0b51754564fdaa624cfbdfb8bf3166417b4"},{"version":"2.1.0-alpha04","sha1":"b428daae88d6608554c05894a03eee094c9bdca"},{"version":"2.1.0-alpha05","sha1":"ac23a726cc3e0f3d6dcbfff6d9f62415e7d95e06"},{"version":"2.1.0-alpha06","sha1":"972c2aa5a701c3e1cb56918df2db2a6a1bbb8ad8"}]},{"package":"navigation-common","versions":[{"version":"2.0.0-rc02","sha1":"4ecde72e547baf728346ba4b3cd65dedce7b1dd3"},{"version":"2.0.0","sha1":"1d559940db6952cdb8007ef2b5833a97be0df971"},{"version":"2.1.0-alpha01","sha1":"bd6027fb62c6fb09d82ce81674a17a27e1626485"},{"version":"2.1.0-alpha02","sha1":"c5b5af52bad3a07f46be5e4c47942029a9f8edde"},{"version":"2.1.0-alpha03","sha1":"6a8e2c0089366683e0fceb0ffc5bc2623fff2802"},{"version":"2.1.0-alpha04","sha1":"68518ac44286d707d5722789f94bc15753ee5d35"},{"version":"2.1.0-alpha05","sha1":"89624bc6c1dd998f47d2658fd60ea799d284aacb"},{"version":"2.1.0-alpha06","sha1":"aaeb55914ce5de8b57a7e8dd8162459078626dc"}]},{"package":"navigation-runtime","versions":[{"version":"2.0.0-rc02","sha1":"c26af284a5cc3bd46f999eb63083dffcf62ddcc3"},{"version":"2.0.0","sha1":"d0e7fc46e60fb1072cc786b23e0a89af73e5728c"},{"version":"2.1.0-alpha01","sha1":"b54bd7a8f0af14678295af186b08a7198fe45faf"},{"version":"2.1.0-alpha02","sha1":"468fe0a23d9c16bad167ecfbc6f1b4a9ba0ce122"},{"version":"2.1.0-alpha03","sha1":"44df1f5309dc335a284a59a6b599b04074cd86c1"},{"version":"2.1.0-alpha04","sha1":"43dcde2022edf3ff0695814a57f0c55a40f9bc78"},{"version":"2.1.0-alpha05","sha1":"f4ec0bc51fa893b7fc920dbe4ec06ee67707a68c"},{"version":"2.1.0-alpha06","sha1":"4c431e36160a02ada26e412f03c510656777e060"}]},{"package":"navigation-runtime-ktx","versions":[{"version":"2.0.0-rc02","sha1":"71e1e01e649ea192f1509b06f29b033f8c26f812"},{"version":"2.0.0","sha1":"1324d0e164ff9e1dcd0bde29c4fc9fa001716d9b"},{"version":"2.1.0-alpha01","sha1":"3506c1a67ac720f9e669e15a8ff282e041b02c75"},{"version":"2.1.0-alpha02","sha1":"3876244288a896d4afaf86d6087462e292d61943"},{"version":"2.1.0-alpha03","sha1":"aaea3f9219d961a981ae5739639491d6434ae107"},{"version":"2.1.0-alpha04","sha1":"bc68c76c51faafa0dc5b473d427227cd0ef2cad7"},{"version":"2.1.0-alpha05","sha1":"8a23f28063220dd4f6f3bc2185a62496ca75b22e"},{"version":"2.1.0-alpha06","sha1":"bac14e3013fbc15ef95b79f4179bb79937802266"}]},{"package":"navigation-common-ktx","versions":[{"version":"2.0.0-rc02","sha1":"1e165f8369576cc4fe69accf5f71a88f1abfd814"},{"version":"2.0.0","sha1":"473e50dc9d6cf12bee03bf158173879cfb198dfe"},{"version":"2.1.0-alpha01","sha1":"a7d372bfc1fd8ee67dc3bc4c46866e78bfc8dda3"},{"version":"2.1.0-alpha02","sha1":"6ce0ad2610eacd5b5df701227fe29ec2ffa09bf1"},{"version":"2.1.0-alpha03","sha1":"b0d73ad832c8c0eae59253af3a1a3729c210d4f3"},{"version":"2.1.0-alpha04","sha1":"6cb24a3243bfa3eb14ed1a89c7184b69aee43597"},{"version":"2.1.0-alpha05","sha1":"3b37ff4b3f35cb07cd49612d0f93040e3cc76fee"},{"version":"2.1.0-alpha06","sha1":"2e8c88b27cd31202c67f6ef1b0a20ba71194e55"}]}]},{"group":"androidx.work","update_time":-1,"packages":[{"package":"work-rxjava2","versions":[{"version":"2.0.0-rc01","sha1":"95df3c0500b3c95662639276b96d23a22b79615f"},{"version":"2.0.0","sha1":"458a16e3adfb180454b39d9f613023c09b7b1df7"},{"version":"2.0.1-rc01","sha1":"8ddf594789d5372224792e2472e1d93e2ea41591"},{"version":"2.0.1","sha1":"dded38c0dc5a85f859aaf9bc7c2dbcbe1319a167"},{"version":"2.1.0-alpha01","sha1":"ef48df92ffe3dae3aa42763189ea7e4592e0ee84"},{"version":"2.1.0-alpha02","sha1":"37b7a8cb948fc8baf2994b7edede82adea0219bf"},{"version":"2.1.0-alpha03","sha1":"f341d5dd75d1e525c85ad55d322f8d69e0aebab8"},{"version":"2.1.0-beta01","sha1":"5615eb92c867cbba0ed7913f96f9016bd196c2b7"},{"version":"2.1.0-beta02","sha1":"efe3ef2f026bd6340e8a3c5a1f94c3a74e52d65f"},{"version":"2.1.0-rc01","sha1":"ca3b4c92d820d67b8bc31507bb26b5f7b2f0dec3"}]},{"package":"work-runtime","versions":[{"version":"2.0.0-rc01","sha1":"19e656a57146636571680ea3869286c8b8783f3e"},{"version":"2.0.0","sha1":"2621046f2d4190dcc0ea38d2c341b5d5f699a02c"},{"version":"2.0.1-rc01","sha1":"e3cc2708f720c98ab8cfc6b478f16dd802fa9809"},{"version":"2.0.1","sha1":"e40ac894a8de0173658ec08e171355f097198afa"},{"version":"2.1.0-alpha01","sha1":"a8adde078dba861a76d943c63449aee672919ee3"},{"version":"2.1.0-alpha02","sha1":"d8aca852316d74e83f23e3edf695cf1d3102e940"},{"version":"2.1.0-alpha03","sha1":"44db78926a360093b56d678de0092c0b9a3f727e"},{"version":"2.1.0-beta01","sha1":"9c00b621054cff28d4167e0ad6c18e96be630cfb"},{"version":"2.1.0-beta02","sha1":"7a17402d64e620257f8523cb1d2e0b6e7e790f19"},{"version":"2.1.0-rc01","sha1":"6db751db44680714470222d20d6115152c9df2c0"}]},{"package":"work-testing","versions":[{"version":"2.0.0-rc01","sha1":"6b29f2d1e9b937c8786d88bad14d3db0e357ba2b"},{"version":"2.0.0","sha1":"54dab19911f168a869fbb9af591cd1db9a9aae1c"},{"version":"2.0.1-rc01","sha1":"ff7c0122389325b06b1cc56931019d7607b3955f"},{"version":"2.0.1","sha1":"d1783517dc36a5443412d7a51516aaaf5723ef5a"},{"version":"2.1.0-alpha01","sha1":"65570c0f7e94746b19075e8e9a3012b8599d469a"},{"version":"2.1.0-alpha02","sha1":"9e5789f2bc731c8fb6a8f9b4ef030d3ef3af746f"},{"version":"2.1.0-alpha03","sha1":"90a466dd5d2c315e9f934ae507da7943bd0aa925"},{"version":"2.1.0-beta01","sha1":"ccfaf33af7cfa3ebded13bb35a8baeef0e756fba"},{"version":"2.1.0-beta02","sha1":"a36a655ed4b289a2fe009ed6dca8f60fed5189ed"},{"version":"2.1.0-rc01","sha1":"3c2b3a7eba333221b7402a9b3895bd6db872bf28"}]},{"package":"work-runtime-ktx","versions":[{"version":"2.0.0-rc01","sha1":"6e0d056ea6527a4d0d238dbe9f51fab182a04f24"},{"version":"2.0.0","sha1":"acb2519201a3b74261195e9276a072e9bf0adcde"},{"version":"2.0.1-rc01","sha1":"f4a3e7559a2d77d9904779c635080faa38924a2d"},{"version":"2.0.1","sha1":"61501b62a7e377d52f66c1cadef31968e87c62e0"},{"version":"2.1.0-alpha01","sha1":"dbb8f80b72ea4e8eb6745420d8b1f9120e00e078"},{"version":"2.1.0-alpha02","sha1":"2a45213510785cdd17eba113817de047150b89d1"},{"version":"2.1.0-alpha03","sha1":"a728f4f111b0d4c8d842699088309736d0c668c1"},{"version":"2.1.0-beta01","sha1":"2fd6235313ac92320f353dffcca1c0004258eaf3"},{"version":"2.1.0-beta02","sha1":"4b540dc7c275e81d06c5f8ecf6f2060b453e65ab"},{"version":"2.1.0-rc01","sha1":"a5d1639187496deaac7b79e9fa15b00a9902157c"}]}]},{"group":"androidx.sharetarget","update_time":-1,"packages":[{"package":"sharetarget","versions":[{"version":"1.0.0-alpha01","sha1":"2f6d02eeae651296844f53036a18ef2587949907"},{"version":"1.0.0-alpha02","sha1":"be06300be2d0485a298fb0660f351f8c0fb490fa"}]}]},{"group":"androidx.enterprise","update_time":-1,"packages":[{"package":"enterprise-feedback","versions":[{"version":"1.0.0-alpha01","sha1":"400d8cee4f3a2c8ce1d476e3769fc0775355f4b9"},{"version":"1.0.0-alpha02","sha1":"b1d92674244ee87db26f6b03bb629649644f5cee"}]},{"package":"enterprise-feedback-testing","versions":[{"version":"1.0.0-alpha02","sha1":"7bf4905f3db8c1592605ccc0776fcf712fd9b7d2"}]}]},{"group":"androidx.camera","update_time":-1,"packages":[{"package":"camera-core","versions":[{"version":"1.0.0-alpha01","sha1":"a8d30a16a4acd4e118a99485ae63ea6980ae487"},{"version":"1.0.0-alpha02","sha1":"99f69d49b771faf4e88e9a66d283ad3edccd1423"},{"version":"1.0.0-alpha03","sha1":"86ae2560a23aa7acd5a0b81754861743d60b8772"}]},{"package":"camera-camera2","versions":[{"version":"1.0.0-alpha01","sha1":"d78a9de7b45330c5ecd433e8e9ae859179463230"},{"version":"1.0.0-alpha02","sha1":"493c970808e0731cbb73c1489e03ae0905e9b2da"},{"version":"1.0.0-alpha03","sha1":"199b77dbef8920d4f002513d1b0ac8aad096060"}]}]},{"group":"androidx.benchmark","update_time":-1,"packages":[{"package":"benchmark-gradle-plugin","versions":[{"version":"1.0.0-alpha01","sha1":"a0e183412247596cc181d6e6da16e641bb239713"},{"version":"1.0.0-alpha02","sha1":"b94c8b80468c4f5d82a47957e860b38207f82ab"},{"version":"1.0.0-alpha03","sha1":"bd3f70bfd986692c937ad6f95f43fd47609e4d3b"}]},{"package":"benchmark","versions":[{"version":"1.0.0-alpha01","sha1":"c0b110ed5e2b12ccf715463d832d7ffca0cfe3aa"},{"version":"1.0.0-alpha02","sha1":"d1ceb06998b7b5d7e6b32c829f445a09acf42f3b"},{"version":"1.0.0-alpha03","sha1":"ff30a4c9d2a7c1859137ac59ad7bf5c3b3bbf9f6"}]}]},{"group":"androidx.security","update_time":-1,"packages":[{"package":"security-crypto","versions":[{"version":"1.0.0-alpha01","sha1":"dd4aaab6132fcea89d29092b6debc9582d6bd9cd"},{"version":"1.0.0-alpha02","sha1":"e1d0d63d70be1576c3895217ee1b1b7d8261a535"}]}]},{"group":"com.google.android.datatransport","update_time":-1,"packages":[{"package":"transport-api","versions":[{"version":"1.0.0","sha1":"6b8fd1ad1ddebfde2be9ddc2502739ac71fb2cc4"},{"version":"2.0.0","sha1":"fe2d223752193e9a16a65534c0d424e67e41e91d"}]},{"package":"transport-backend-cct","versions":[{"version":"1.0.0","sha1":"1c6fd6293aa990e54b55b3418908fbb1501186e7"},{"version":"2.0.0","sha1":"bbe5a361aa14b9f51faa542e12f0f3e2da63f9ca"},{"version":"2.0.1","sha1":"f04025ba44589febcd005347149d60fab5150ce5"}]},{"package":"transport-runtime","versions":[{"version":"1.0.0","sha1":"65b934d5c5bbc45150f79c29cc6fbc0efb5f27df"},{"version":"2.0.0","sha1":"ad9a22e21997f7e824356b430313372e4128891f"}]}]},{"group":"zipflinger","update_time":-1,"packages":[{"package":"zipflinger","versions":[{"version":"3.6.0-alpha04","sha1":"c8862041939c96432bc5286ee2dcbae4e486cb67"}]}]},{"group":"androidx.autofill","update_time":-1,"packages":[{"package":"autofill","versions":[{"version":"1.0.0-alpha01","sha1":"20410aa7a4b3de1b54405364637f039119c32697"}]}]}]} \ No newline at end of file diff --git a/play-services-resolver-1.2.111.0.unitypackage b/play-services-resolver-1.2.111.0.unitypackage deleted file mode 100644 index 5237d016..00000000 Binary files a/play-services-resolver-1.2.111.0.unitypackage and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager.meta similarity index 52% rename from exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb.meta rename to plugin/Assets/ExternalDependencyManager.meta index b48b4de1..1a04a8e0 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb.meta +++ b/plugin/Assets/ExternalDependencyManager.meta @@ -1,9 +1,7 @@ fileFormatVersion: 2 -guid: 773f15b8762c473ea3fba0fd506fb73f -labels: -- gvh_version-1.2.111.0 -- gvh -timeCreated: 1538009133 +guid: e7f679112961a0f7f11fdb3f983aed77 +folderAsset: yes +timeCreated: 1448926447 licenseType: Pro DefaultImporter: userData: diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.111.0.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor.meta similarity index 52% rename from exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.111.0.dll.mdb.meta rename to plugin/Assets/ExternalDependencyManager/Editor.meta index bf2313f1..4ef59616 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.111.0.dll.mdb.meta +++ b/plugin/Assets/ExternalDependencyManager/Editor.meta @@ -1,9 +1,7 @@ fileFormatVersion: 2 -guid: 69b01d69b0104e61849f3efbb255cd2b -labels: -- gvh_version-1.2.111.0 -- gvh -timeCreated: 1538009133 +guid: b42aa8acaabecbf943da2892de5e6aeb +folderAsset: yes +timeCreated: 1448926516 licenseType: Pro DefaultImporter: userData: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta b/plugin/Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta new file mode 100644 index 00000000..e5662a98 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: aba4ee01c6d145f7bf2d944d892f709a +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/CHANGELOG.md +- gvh +timeCreated: 1584567712 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.dll.mdb.meta similarity index 50% rename from plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.mdb.meta rename to plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.dll.mdb.meta index bf2313f1..3e39629c 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.mdb.meta +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.dll.mdb.meta @@ -1,7 +1,8 @@ fileFormatVersion: 2 -guid: 69b01d69b0104e61849f3efbb255cd2b +guid: adacdf2f31cf474c99788c9454063fed labels: -- gvh_version-1.2.111.0 +- gvh_version-1.2.177 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.177/Google.IOSResolver.dll.mdb - gvh timeCreated: 1538009133 licenseType: Pro diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.dll.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.dll.meta new file mode 100644 index 00000000..707c589b --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.dll.meta @@ -0,0 +1,36 @@ +fileFormatVersion: 2 +guid: e2d7ea0845de4cf984265d2a444b7aa4 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.dll +- gvh +- gvhp_targets-editor +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + validateReferences: 0 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.pdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.pdb.meta new file mode 100644 index 00000000..e6b1e059 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: baf24db2bf904e729e7796721c09e8ad +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.IOSResolver.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.mdb.meta similarity index 50% rename from plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.mdb.meta rename to plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.mdb.meta index cdaa43e1..e5cd206a 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.mdb.meta +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.mdb.meta @@ -1,7 +1,8 @@ fileFormatVersion: 2 -guid: 8e9ed466b9864cdb9e8be1320fa4b56b +guid: c613343662334614b65918fa6cf9c17e labels: -- gvh_version-1.2.111.0 +- gvh_version-1.2.177 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.177/Google.JarResolver.dll.mdb - gvh timeCreated: 1538009133 licenseType: Pro diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.111.0.dll.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.meta similarity index 76% rename from exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.111.0.dll.meta rename to plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.meta index f51e9178..a1398e9f 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.111.0.dll.meta +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.meta @@ -1,9 +1,10 @@ fileFormatVersion: 2 -guid: c16d9c05cc6d4e32be3b86440c709558 +guid: fa49a85d4ba140a0ae21528ed12d174c labels: -- gvh_version-1.2.111.0 +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll - gvh -- gvh_targets-editor +- gvhp_targets-editor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.pdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.pdb.meta new file mode 100644 index 00000000..12826037 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d13c8602d5e14e43b0e92459754c4315 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.dll.mdb.meta new file mode 100644 index 00000000..9c80a012 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.dll.mdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b60e304f823d423da748809d088d67b1 +labels: +- gvh_version-1.2.177 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.177/Google.PackageManagerResolver.dll.mdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.dll.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.dll.meta new file mode 100644 index 00000000..3b4fa84b --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.dll.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: d8bb10c56a0147bc855a6296778e025e +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.dll +- gvh +- gvhp_targets-editor +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.pdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.pdb.meta new file mode 100644 index 00000000..ac071adc --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a695eb9f64fe49569a2db0c4246c877d +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.PackageManagerResolver.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb.meta similarity index 51% rename from plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb.meta rename to plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb.meta index b48b4de1..442c422b 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb.meta +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb.meta @@ -1,7 +1,8 @@ fileFormatVersion: 2 -guid: 773f15b8762c473ea3fba0fd506fb73f +guid: 5855ffeab65945dc8f9cb3dc063f9eba labels: -- gvh_version-1.2.111.0 +- gvh_version-1.2.177 +- gvhp_exportpath-ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb - gvh timeCreated: 1538009133 licenseType: Pro diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta new file mode 100644 index 00000000..3babd47f --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: f7632a50b10045458c53a5ddf7b6d238 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/Google.VersionHandler.dll +- gvh +- gvhp_targets-editor +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta new file mode 100644 index 00000000..0b461abd --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 57f5a82a79ab4b098f09326c8f3c73a6 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/Google.VersionHandler.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.dll.mdb.meta new file mode 100644 index 00000000..6569dce4 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.dll.mdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: bd203ff120ed4c69bfdeffa466beec72 +labels: +- gvh_version-1.2.177 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.177/Google.VersionHandlerImpl.dll.mdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.dll.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.dll.meta new file mode 100644 index 00000000..7456068f --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.dll.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: 5980a684c61d42fbb6b74e2eb3477016 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll +- gvh +- gvhp_targets-editor +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.pdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.pdb.meta new file mode 100644 index 00000000..da5b09b2 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl.pdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9f56badf3ca84753b00163c3b632d4e5 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.pdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/LICENSE.meta b/plugin/Assets/ExternalDependencyManager/Editor/LICENSE.meta new file mode 100644 index 00000000..30482451 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/LICENSE.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ae8b2bc8d1ac4ad48f0ab2b2e7ac75fb +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/LICENSE +- gvh +timeCreated: 1584567712 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/README.md.meta b/plugin/Assets/ExternalDependencyManager/Editor/README.md.meta new file mode 100644 index 00000000..6bcc2245 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/README.md.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 77919e84cef8419ab4b725fc16e83d52 +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/README.md +- gvh +timeCreated: 1584567712 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/external-dependency-manager.txt.meta b/plugin/Assets/ExternalDependencyManager/Editor/external-dependency-manager.txt.meta new file mode 100644 index 00000000..0a44bf71 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/external-dependency-manager.txt.meta @@ -0,0 +1,15 @@ +fileFormatVersion: 2 +guid: 20a3aeaa6495bf32b31b6980c7977b87 +labels: +- gvh_version-0.0.0.0 +- gvhp_exportpath-ExternalDependencyManager/Editor/external-dependency-manager.txt +- gvh +- gvh_manifest +- gvhp_manifestname-0External Dependency Manager +- gvhp_manifestname-play-services-resolver +timeCreated: 1474401009 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.PackageManager.dll.meta b/plugin/Assets/PlayServicesResolver/Editor/Google.PackageManager.dll.meta deleted file mode 100644 index 82c309a5..00000000 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.PackageManager.dll.meta +++ /dev/null @@ -1,24 +0,0 @@ -fileFormatVersion: 2 -guid: 1d2d3ae96d464e3a87349618b43303e3 -labels: -- gvh_version-0.0.0.0 -- gvh -- gvh_targets-editor -timeCreated: 1473798236 -licenseType: Pro -PluginImporter: - serializedVersion: 1 - iconMap: {} - executionOrder: {} - isPreloaded: 0 - platformData: - Any: - enabled: 0 - settings: {} - Editor: - enabled: 1 - settings: - DefaultValueInitialized: true - userData: - assetBundleName: - assetBundleVariant: diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl.dll.mdb.meta b/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl.dll.mdb.meta deleted file mode 100644 index 9e6d261e..00000000 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl.dll.mdb.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 9bfab54114734b7884fe507e7a19b45f -labels: -- gvh_version-1.2.111.0 -- gvh -timeCreated: 1538009133 -licenseType: Pro -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver.txt.meta b/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver.txt.meta index 47f5ad9d..af4c6c44 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver.txt.meta +++ b/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver.txt.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 guid: ba6f911c6f9d4d9ea269756e9dafb641 labels: -- gvh_version-0.0.0.0 +- gvh_version-1.2.137.0 - gvh - gvh_manifest timeCreated: 1474401009 diff --git a/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt b/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt new file mode 100644 index 00000000..a0268fcc --- /dev/null +++ b/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt @@ -0,0 +1,2 @@ +Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll +Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.mdb diff --git a/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta b/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta new file mode 100644 index 00000000..af4c6c44 --- /dev/null +++ b/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ba6f911c6f9d4d9ea269756e9dafb641 +labels: +- gvh_version-1.2.137.0 +- gvh +- gvh_manifest +timeCreated: 1474401009 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/registry/com.google.unity.example/gpm-example-plugin/1.0.0/description.xml b/registry/com.google.unity.example/gpm-example-plugin/1.0.0/description.xml deleted file mode 100644 index a0fcd226..00000000 --- a/registry/com.google.unity.example/gpm-example-plugin/1.0.0/description.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - GPM Example Plugin - A demonstration of a Google Package Manager compatable plugin. - This plugin demonstrates how a Unity plugin can be created with the Google Package Manager Packager and be available from a Google Package Manager registry. - - - diff --git a/registry/com.google.unity.example/gpm-example-plugin/1.0.0/gpm-example-plugin.unitypackage b/registry/com.google.unity.example/gpm-example-plugin/1.0.0/gpm-example-plugin.unitypackage deleted file mode 100644 index b89606e1..00000000 Binary files a/registry/com.google.unity.example/gpm-example-plugin/1.0.0/gpm-example-plugin.unitypackage and /dev/null differ diff --git a/registry/com.google.unity.example/package-manifest.xml b/registry/com.google.unity.example/package-manifest.xml deleted file mode 100644 index bc845273..00000000 --- a/registry/com.google.unity.example/package-manifest.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - com.google.unity.example - gpm-example-plugin - unitypackage - 1.0.0 - - 1.0.0 - - 1.0.0 - - - 0 - diff --git a/registry/com.google.unity.firebase.analytics/firebase-analytics-plugin/1.1.0/description.xml b/registry/com.google.unity.firebase.analytics/firebase-analytics-plugin/1.1.0/description.xml deleted file mode 100755 index 9e9f819a..00000000 --- a/registry/com.google.unity.firebase.analytics/firebase-analytics-plugin/1.1.0/description.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Google Firebase Analytics - Official Google Firebase Analytics plugin for Unity - TODO: add full description - - - \ No newline at end of file diff --git a/registry/com.google.unity.firebase.analytics/firebase-analytics-plugin/1.1.0/firebase-analytics-plugin.unitypackage b/registry/com.google.unity.firebase.analytics/firebase-analytics-plugin/1.1.0/firebase-analytics-plugin.unitypackage deleted file mode 100755 index 16122b92..00000000 Binary files a/registry/com.google.unity.firebase.analytics/firebase-analytics-plugin/1.1.0/firebase-analytics-plugin.unitypackage and /dev/null differ diff --git a/registry/com.google.unity.firebase.analytics/package-manifest.xml b/registry/com.google.unity.firebase.analytics/package-manifest.xml deleted file mode 100755 index ab9902bf..00000000 --- a/registry/com.google.unity.firebase.analytics/package-manifest.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - com.google.unity.firebase.analytics - firebase-analytics-plugin - 1.1.0 - unitypackage - - 1.1.0 - - 1.1.0 - - - 0 - \ No newline at end of file diff --git a/registry/com.google.unity.playgames/google-playgames-plugin/0.9.36/description.xml b/registry/com.google.unity.playgames/google-playgames-plugin/0.9.36/description.xml deleted file mode 100755 index e414088b..00000000 --- a/registry/com.google.unity.playgames/google-playgames-plugin/0.9.36/description.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Google Play Games Services - Official Google Play Games plugin for Unity - The Google Play Games plugin for Unity® is an open-source project whose goal is to provide a plugin that allows game developers to integrate with the Google Play Games API from a game written in Unity®. However, this project is not in any way endorsed or supervised by Unity Technologies. Unity® is a trademark of Unity Technologies. iOS is a trademark of Apple, Inc. - - - \ No newline at end of file diff --git a/registry/com.google.unity.playgames/google-playgames-plugin/0.9.36/google-playgames-plugin.unitypackage b/registry/com.google.unity.playgames/google-playgames-plugin/0.9.36/google-playgames-plugin.unitypackage deleted file mode 100755 index a4583c89..00000000 Binary files a/registry/com.google.unity.playgames/google-playgames-plugin/0.9.36/google-playgames-plugin.unitypackage and /dev/null differ diff --git a/registry/com.google.unity.playgames/package-manifest.xml b/registry/com.google.unity.playgames/package-manifest.xml deleted file mode 100755 index dc142d5e..00000000 --- a/registry/com.google.unity.playgames/package-manifest.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - com.google.unity.playgames - google-playgames-plugin - unitypackage - 0.9.36 - - 0.9.36 - - 0.9.36 - - - 0 - diff --git a/registry/registry.xml b/registry/registry.xml deleted file mode 100644 index ff49d0e7..00000000 --- a/registry/registry.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - registry.google.unity - jarresolver-google-registry - 0.0.1.1 - 1482171836 - - com.google.unity.example - com.google.unity.playgames - com.google.unity.firebase.analytics - - diff --git a/sample/Assets/PlayServicesResolver/Editor/ResolutionRunner.cs b/sample/Assets/ExternalDependencyManager/Editor/ResolutionRunner.cs similarity index 100% rename from sample/Assets/PlayServicesResolver/Editor/ResolutionRunner.cs rename to sample/Assets/ExternalDependencyManager/Editor/ResolutionRunner.cs diff --git a/sample/Assets/PlayServicesResolver/Editor/Resolver.cs b/sample/Assets/ExternalDependencyManager/Editor/Resolver.cs similarity index 100% rename from sample/Assets/PlayServicesResolver/Editor/Resolver.cs rename to sample/Assets/ExternalDependencyManager/Editor/Resolver.cs diff --git a/sample/Assets/PlayServicesResolver/Editor/SampleDependencies.cs b/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.cs similarity index 100% rename from sample/Assets/PlayServicesResolver/Editor/SampleDependencies.cs rename to sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.cs diff --git a/sample/Assets/PlayServicesResolver/Editor/SampleDependencies.xml b/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.xml similarity index 94% rename from sample/Assets/PlayServicesResolver/Editor/SampleDependencies.xml rename to sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.xml index de11de0b..97c27dec 100644 --- a/sample/Assets/PlayServicesResolver/Editor/SampleDependencies.xml +++ b/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.xml @@ -67,6 +67,9 @@ generated Xcode project. This is "true" by default. * "minTargetSdk" (optional) The minimum iOS SDK required by this Cocoapod. + * "addToAllTargets" (optional) + Whether to add this pod to all targets when multiple target is + supported. This is "false" by default. * "configurations" (optional) Podfile formatted list of configurations to include this pod in. * "modular_headers" (optional) @@ -77,7 +80,7 @@ Subspecs to include for the pod. --> + minTargetSdk="6.0" addToAllTargets="false"> + + + + + + + + com.mycompany + + + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..41cb7657 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,17 @@ +/* + * Copyright 2019 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +rootProject.name = "playServicesResolver" diff --git a/source/PlayServicesResolver/PlayServicesResolver.csproj b/source/AndroidResolver/AndroidResolver.csproj similarity index 93% rename from source/PlayServicesResolver/PlayServicesResolver.csproj rename to source/AndroidResolver/AndroidResolver.csproj index 4c1cc52d..08059780 100644 --- a/source/PlayServicesResolver/PlayServicesResolver.csproj +++ b/source/AndroidResolver/AndroidResolver.csproj @@ -53,20 +53,19 @@ - - + + - + - @@ -87,6 +86,9 @@ scripts\download_artifacts.gradle + + scripts\settings.gradle + diff --git a/source/PlayServicesResolver/Properties/AssemblyInfo.cs b/source/AndroidResolver/Properties/AssemblyInfo.cs similarity index 93% rename from source/PlayServicesResolver/Properties/AssemblyInfo.cs rename to source/AndroidResolver/Properties/AssemblyInfo.cs index b4787d66..2d9fbd82 100644 --- a/source/PlayServicesResolver/Properties/AssemblyInfo.cs +++ b/source/AndroidResolver/Properties/AssemblyInfo.cs @@ -55,4 +55,6 @@ // Uses XmlDependencies class. [assembly: InternalsVisibleTo("Google.IOSResolver")] - +// Uses all classes for testing. +[assembly: InternalsVisibleTo("Google.AndroidResolverIntegrationTests")] +[assembly: InternalsVisibleTo("Google.AndroidResolverTests")] diff --git a/source/AndroidResolver/scripts/build.gradle b/source/AndroidResolver/scripts/build.gradle new file mode 100644 index 00000000..fd706dab --- /dev/null +++ b/source/AndroidResolver/scripts/build.gradle @@ -0,0 +1,17 @@ +/* + * Copyright 2019 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply from: "download_artifacts_test.gradle" diff --git a/source/PlayServicesResolver/scripts/download_artifacts.gradle b/source/AndroidResolver/scripts/download_artifacts.gradle similarity index 76% rename from source/PlayServicesResolver/scripts/download_artifacts.gradle rename to source/AndroidResolver/scripts/download_artifacts.gradle index 9226d810..7d99ec35 100644 --- a/source/PlayServicesResolver/scripts/download_artifacts.gradle +++ b/source/AndroidResolver/scripts/download_artifacts.gradle @@ -14,10 +14,6 @@ * limitations under the License. */ -import groovy.transform.AutoClone -import groovy.transform.EqualsAndHashCode -import groovy.transform.ToString - String helpText = """ This Gradle script performs the following: - Adds Android maven repositories to search locations for artifacts. @@ -46,8 +42,15 @@ USE_MAVEN_LOCAL_REPO (optional project property): By default local maven repositories are enabled. USE_REMOTE_MAVEN_REPOS (optional project property): Optional property which, when not set to 1, disables the implicit use of - remote repositories (maven.google.com, jcenter and maven central) when + remote repositories (maven.google.com and maven central) when fetching artifacts. By default remote repositories are enabled. +USE_JETIFIER (optional project property): + Optional project which, when not set to 1, disables the Jetifier tool to + convert references to the legacy Android Support libraries to Jetpack + (AndroidX). This defaults to 0. +DATA_BINDING_VERSION (required when USE_JETIFIER is 1): + Data binding library version to use when applying conversions from the legacy + Android support libraries to Jetpack (AndroidX). PACKAGES_TO_COPY (required project property): Semicolon separated list of Maven artifact specifications. This will result in the script attempting to download the set of artifacts to @@ -67,10 +70,22 @@ buildscript { repositories { mavenLocal() mavenCentral() - jcenter() + google() + } + dependencies { + classpath "com.android.tools.build.jetifier:jetifier-processor:1.0.+" } } +import groovy.transform.AutoClone +import groovy.transform.EqualsAndHashCode +import groovy.transform.ToString + +import com.android.tools.build.jetifier.core.config.Config +import com.android.tools.build.jetifier.core.config.ConfigParser +import com.android.tools.build.jetifier.processor.FileMapping +import com.android.tools.build.jetifier.processor.Processor + configurations { // Configuration used to resolve the final set of transitive dependencies. transitivePackagesConfig @@ -82,13 +97,13 @@ configurations { @ToString(includeNames=true, includeFields=true) public class Version implements Comparable { // Version string to parse. - public String version + public String version = "" /* * Construct a version class from a string. */ public Version(String version = null) { - this.version = version + this.version = version ? version : "" } /* @@ -268,13 +283,14 @@ public enum VersionExpressionMatchType { @ToString(includeNames=true, includeFields=true) public class VersionRange { // Parsed version expression. - public String versionExpression + public String versionExpression = "" // Minimum version parsed from a version expression. - public String minimum + public String minimum = "0" // Maximum version parsed from a version expression. - public String maximum + public String maximum = "+" // Type of version expression parsed. - public VersionExpressionMatchType matchType + public VersionExpressionMatchType matchType = + VersionExpressionMatchType.NONE /* * Get the minimum and maximum version from a maven / ivy version expression. @@ -394,6 +410,8 @@ public class VersionRange { * @returns true if the version is in range, false otherwise. */ boolean inRange(Version version) { + // If there are no constraints or the version is empty, always match. + if (!versionExpression || !version.version) return true switch (matchType) { case VersionExpressionMatchType.NONE: return false @@ -415,36 +433,53 @@ public class VersionRange { @ToString(includeNames=true, includeFields=true) public class PackageSpecifier implements Comparable { // Group component of a maven package. - public String group + public String group = "" // Artifact component of a maven package. - public String artifact + public String artifact = "" // Version expression of a maven package. - public String versionExpression + public String versionExpression = "" + // Classifier of the artifact. + public String classifier = "" // Type of the artifact. - public String artifactType + public String artifactType = "" /* * Extract the components of a package specifier string. * * @param packageSpecifier Package specification. - * Package specification should match agroup:apackage:version@artifacttype - * e.g a.b.c:d.e:1.2.3@aar + * Package specification should match one of the following formats, + * - agroup:apackage:version@artifacttype + * e.g a.b.c:d.e:1.2.3@aar + * - agroup:apackage:version:classifier@artifacttype + * e.g a.b.c:d.e:1.2.3:f@aar * - * @returns [group, artifact, version, artifactType] list with the components + * @returns [group, artifact, version, artifactType, classifier] list with the components * of the package spec. If a component is not present the entry in the list * is null. */ public static PackageSpecifier fromString(String packageSpecifierString) { List components = packageSpecifierString ? packageSpecifierString.split(/[:@]/) : [] + if (components.size() == 5) { + // Special case to handle group:artifact:version:classifier@artifactType, + // We want to maintain the components list as, + // [group, artifact, version, artifactType, classifier] + // because classifiers are a rare case and more often than not, the parser + // will find artifactType in the second to last position. + // Hence, if there are 5 components, swap the last two to make sure + // artifactType is second to last. + components.swap(3,4) + } // Fill the list of components with null elements. - components += ([null] * Math.min(4, (4 - components.size()))) + components += ([""] * Math.min(5, (5 - components.size()))) + return new PackageSpecifier( group: components[0], artifact: components[1], versionExpression: components[2] ? (VersionRange.fromExpression( - components[2])).expression : null, - artifactType: components[3]) + components[2])).expression : "", + artifactType: components[3], + classifier: components[4]) } /* @@ -473,7 +508,7 @@ public class PackageSpecifier implements Comparable { group: group, artifact: module, versionExpression: (version ? - (VersionRange.fromExpression(version)).expression : null)) + (VersionRange.fromExpression(version)).expression : "")) } } @@ -489,6 +524,7 @@ public class PackageSpecifier implements Comparable { return resolvedArtifact.with { PackageSpecifier pkg = fromModuleVersionIdentifier(moduleVersion.id) pkg.artifactType = type + pkg.classifier = classifier return pkg } } @@ -507,7 +543,26 @@ public class PackageSpecifier implements Comparable { group: group, artifact: name, versionExpression: (version ? - (VersionRange.fromExpression(version)).expression : null)) + (VersionRange.fromExpression(version)).expression : "")) + } + } + + /* + * Convert a Gradle DirectDependencyMetadata to a PackageSpecifier. + * + * @param directDependencyMetadata DirectDependencyMetadata to convert. + * + * @returns PackageSpecifier instance. + */ + public static PackageSpecifier fromDirectDependencyMetadata( + DirectDependencyMetadata directDependencyMetadata) { + return directDependencyMetadata.with { + return new PackageSpecifier( + group: group, + artifact: name, + versionExpression: (versionConstraint ? + (VersionRange.fromExpression( + versionConstraint.requiredVersion).expression) : "")) } } @@ -530,12 +585,11 @@ public class PackageSpecifier implements Comparable { /* * Get the range from the version. * - * @returns Version range parsed from version component of this class or null - * if no version is set. + * @returns Version range parsed from version component of this class or an + * empty string if no version is set. */ public VersionRange getVersionRange() { - return versionExpression ? - VersionRange.fromExpression(versionExpression) : null + return VersionRange.fromExpression(versionExpression) } /* @@ -576,7 +630,11 @@ public class PackageSpecifier implements Comparable { components.add(artifact) if (versionExpression) { if (artifactType) { - components.add(versionExpression + "@" + artifactType) + if (classifier) { + components.add(versionExpression + ':' + classifier + "@" + artifactType) + } else { + components.add(versionExpression + "@" + artifactType) + } } else { components.add(versionExpression) } @@ -628,6 +686,9 @@ public class PackageSpecifier implements Comparable { if (versionExpression) { hypenSeparatedComponents += [versionExpression] } + if (classifier) { + hypenSeparatedComponents += [classifier] + } String filename = hypenSeparatedComponents.join("-") if (artifactType) { filename += "." + (artifactType == "srcaar" ? "aar" : artifactType) @@ -693,6 +754,284 @@ public class PackageSpecifier implements Comparable { } } +// Maps a package to another package name. +public class PackageMapper { + // Map of "group:artifact" to absolute package spec (coordinate) in the form + // "group:artifact:version". + public Map dependenciesMap = [:] + // Inverse mapping of absolute package spec in the form "group:artifact" to + // "group:artifact". This is the inverse of dependenciesMap. + private Map inverseDependenciesMap = null + // Set of packages that should be ignored when applying mapping in the form + // "group:artifact". + public Set blackList = [].toSet() + + /* + * Apply this mapping to the specified package. + * + * @param dependenciesMap Package mapping to apply. This maps strings of the + * form "group:artifact" to absolute package specs (coordinates) + * "group:artifact:version". + * @param blackList Packages to *not* map in the form "group:artifact". + * @param pkg Package specification to map. + * + * @return Package specification mapped via the dependenciesMap. + */ + public PackageSpecifier map(PackageSpecifier pkg) { + String groupArtifact = pkg.groupArtifactString + if (!blackList.contains(groupArtifact)) { + String mappedPackageSpec = dependenciesMap[groupArtifact] + if (mappedPackageSpec) { + return PackageSpecifier.fromString(mappedPackageSpec) + } + } + return pkg + } + + /* + * Lookup the inverse mapping of a package to the original "group:artifact". + * + * @param pkg Package specification to apply reverse mapping. + * + * @return Package specification before mapping was applied. + */ + public PackageSpecifier inverseMap(PackageSpecifier pkg) { + String groupArtifact = pkg.groupArtifactString + if (!blackList.contains(groupArtifact)) { + if (inverseDependenciesMap == null) { + inverseDependenciesMap = dependenciesMap.collectEntries { + String sourceGroupArtifact, String targetPackageSpec -> + return [PackageSpecifier.fromString( + targetPackageSpec).groupArtifactString, + sourceGroupArtifact] + } + } + String mappedPackageSpec = inverseDependenciesMap[groupArtifact] + if (mappedPackageSpec) { + return PackageSpecifier.fromString(mappedPackageSpec) + } + } + return PackageSpecifier.fromString(groupArtifact) + } + + /* + * Apply this mapping to the specified Gradle project configuration. + * + * Substitutes dependencies that match this mapping in the specified + * configuration. + * + * @param configuration Configuration to apply the mapping to. + * @param project Project to apply the configuration to. This is only + * required to apply a workaround for + * https://github.com/gradle/gradle/issues/5174 . + */ + public void applyToProjectConfiguration(Configuration configuration, + Project project) { + // Workaround https://github.com/gradle/gradle/issues/5174 by remapping all + // indirect dependency metadata directly. + // When #5174 is resolved it should be possible to remove this in preference + // of the dependency closure below. + project.dependencies.components.all { ComponentMetadataDetails component -> + component.allVariants { VariantMetadata variant -> + variant.withDependencies { + DirectDependenciesMetadata dependenciesMetadata -> + List currentDeps = dependenciesMetadata.collect { + PackageSpecifier.fromDirectDependencyMetadata(it) + } + Set depsToRemove = [].toSet() + List depsToAdd = currentDeps.findResults { + PackageSpecifier mapped = map(it) + if (mapped != it) { + depsToRemove.add(it) + return mapped + } + return null + } + // dependenciesMetadata.removeAll(oldDeps) does not work, so + // remove each old dependency manually. + depsToRemove.forEach { PackageSpecifier remove -> + dependenciesMetadata.removeIf { + DirectDependencyMetadata dependencyMetadata -> + return PackageSpecifier.fromDirectDependencyMetadata( + dependencyMetadata) == remove + } + } + depsToAdd.forEach { dependenciesMetadata.add(it.specString) } + } + } + } + // Use the dependency substitution closure to apply replacements. + configuration.resolutionStrategy.dependencySubstitution.all { + DependencySubstitution dependencySubstitution -> + ModuleComponentSelector requestedSelector = + dependencySubstitution.requested as ModuleComponentSelector + if (requestedSelector) { + PackageSpecifier requested = + PackageSpecifier.fromModuleComponentSelector(requestedSelector) + PackageSpecifier mapped = map(requested) + if (requested != mapped) { + dependencySubstitution.useTarget(mapped.specString, + "USE_JETIFIER is enabled") + } + } + } + } + + /* + * Create a mapper using a Jetifier (Jetpack / AndroidX) processor. + * + * @param dataBindingVersion Version of the Jetpack Data Binding Library to + * use. All components of the data binding library must be pinned to the same + * version. These libraries are versioned in lock step with the Android Gradle + * plugin. + * https://android.googlesource.com/platform/tools/base/+/\ + * f831317e99f/build-system/gradle-core/src/main/java/com/\ + * android/build/gradle/internal/dependency/\ + * AndroidXDepedencySubstitution.kt#56 + * + * @returns PackageMapper initialized with the Jetpack processor's mapping. + */ + public static PackageMapper fromJetifierProcessor( + String dataBindingVersion) { + Processor processor = Processor.Companion.newInstance().createProcessor3( + (new ConfigParser()).loadDefaultConfig(), /* config */ + false, /* reversedMode */ + false, /* rewritingSupportLib */ + true, /* useFallbackIfTypeIsMissing */ + false, /* allowAmbiguousPackages */ + false, /* stripSignatures */ + dataBindingVersion /* dataBindingVersion */) + Map jetifierDependenciesMap = processor.getDependenciesMap( + false /* filterOutBaseLibrary */) + Set blackList = [ + // androidx.databinding:databinding-compiler has a transitive dependency + // on com.android.databinding:baseLibrary, which shouldn't be replaced + // with AndroidX. + // https://issuetracker.google.com/78202536 + // https://android.googlesource.com/platform/tools/base/+/\ + // f831317e99f/build-system/gradle-core/src/main/java/com/\ + // android/build/gradle/internal/dependency/\ + // AndroidXDepedencySubstitution.kt#143 + "com.android.databinding:baseLibrary", + ].toSet() + return new PackageMapper(dependenciesMap: jetifierDependenciesMap, + blackList: blackList) + } +} + +// Processor which simply copies a file to an output path. +public class DefaultPackageProcessor { + /* + * Generate a task to copy a file. + * + * @param project Project to add the task to. + * @param artifact Artifact to copy. + * @param pkg Package specifier of the artifact to copy. + * @param artifactTargetFile Path to copy to. + * @param copiedFileArtifacts List to add copied file and package specifier + * when the copy task is complete. + */ + public Task createTask( + Project project, ResolvedArtifact artifact, PackageSpecifier pkg, + File artifactTargetFile, + List> copiedFileArtifacts) { + Task copyTask = project.tasks.create( + name: "copy_" + pkg.filename, + type: Copy, + description: sprintf("Copy %s (%s) to %s", pkg.specString, + artifact.file, artifactTargetFile)) + copyTask.with { + from artifact.file + into artifactTargetFile.parent + rename( + // Rename the file to the target filename and log the copied file & + // artifact so that they can be summarized when all copy artifact + // tasks are complete. + { + String filename -> + copiedFileArtifacts.add( + new Tuple2(artifactTargetFile, pkg)) + return artifactTargetFile.toString() + } + ) + doFirst { project.logger.info(description) } + } + return copyTask + } +} + +// Processor an Android library to reference Jetpack / AndroidX using the +// Jetifier. +public class JetpackPackageProcessor extends DefaultPackageProcessor { + // Jetifier processor. + private Processor processor; + + /* + * Construct a Jetifier (Jetpack / AndroidX) processor. + * + * @param dataBindingVersion Version of the Jetpack Data Binding Library to + * use. All components of the data binding library must be pinned to the same + * version. These libraries are versioned in lock step with the Android Gradle + * plugin. + */ + JetpackPackageProcessor(String dataBindingVersion) { + // NOTE: This needs to strip library signatures as libraries will be + // rewritten and without the signing key it's not possible to resign them. + processor = Processor.Companion.newInstance().createProcessor3( + (new ConfigParser()).loadDefaultConfig(), /* config */ + false, /* reversedMode */ + false, /* rewritingSupportLib */ + true, /* useFallbackIfTypeIsMissing */ + false, /* allowAmbiguousPackages */ + true, /* stripSignatures */ + dataBindingVersion /* dataBindingVersion */) + } + + /* + * Generate a task to process a file. + * + * @param project Project to add the task to. + * @param artifact Artifact to copy. + * @param pkg Package specifier of the artifact to copy. + * @param artifactTargetFile Path to copy to. + * @param copiedFileArtifacts List to add copied file and package specifier + * when the copy task is complete. + */ + public Task createTask( + Project project, ResolvedArtifact artifact, PackageSpecifier pkg, + File artifactTargetFile, + List> copiedFileArtifacts) { + // If this is a file that should not be processed, (i.e is a new AndroidX + // library or an old support library that has been blacklisted) create a + // copy task instead. + if (processor.isNewDependencyFile(artifact.file) || + processor.isOldDependencyFile(artifact.file)) { + return super.createTask(project, artifact, pkg, artifactTargetFile, + copiedFileArtifacts) + } + Task processTask = project.tasks.create( + name: "process_" + pkg.filename, + type: Task, + description: sprintf("Jetify %s (%s) to %s", pkg.specString, + artifact.file, artifactTargetFile)) + processTask.with { + inputs.file artifact.file + outputs.file artifactTargetFile + doFirst { project.logger.info(description) } + doLast { + Set sourceAndTargetFile = + [new FileMapping(artifact.file, /* from */ + artifactTargetFile /* to */)].toSet() + processor.transform(sourceAndTargetFile, /* inputs */ + true /* copyUnmodifiedLibsAlso */) + copiedFileArtifacts.add( + new Tuple2(artifactTargetFile, pkg)) + } + } + return processTask + } +} + /* * Get the group of a version-locked package. * @@ -732,6 +1071,11 @@ int getVersionLockedPackageIndex(PackageSpecifier pkg) { if (specString ==~ /^com\.google\.firebase:[^:]+-unity:.*/) { continue } + // com.android.support:multidex is versioned independently of other + // legacy Android support libraries. + if (specString ==~ /^com\.android\.support:multidex:.*/) { + continue + } // Version 15+ of Google Play Services components are released and // versioned individually. if (pkg.group && pkg.group ==~ /^com\.google\.(android\.gms|firebase)$/ && @@ -1025,6 +1369,7 @@ Tuple2> loosenVersionContraintsForConflicts( new PackageSpecifier( group: pkg.group, artifact: pkg.artifact, + classifier: pkg.classifier, versionExpression: ( isConflicting ? pkg.versionRange.matchType == VersionExpressionMatchType.RANGE ? @@ -1164,27 +1509,32 @@ Tuple2> loosenVersionContraintsForConflicts( conflictPaths.each { logger.quiet(sprintf("- %s", it)) } } } - // Dump the current dependency graph and the conflict state of each node. - summaryGraph.each { - DependencyResult dependency, List parents -> - String groupArtifact = - PackageSpecifier.fromModuleComponentSelector( - dependency.requested as ModuleComponentSelector).groupArtifactString - logger.info( - sprintf( - "%s* %s (new: %s, conflicting: %b (parents: %s))", - " ".multiply(parents.size()), - (dependency instanceof ResolvedDependencyResult) && - dependency.selected.toString() != dependency.requested.toString() ? - sprintf("%s --> %s", dependency.requested.toString(), - dependency.selected.toString()) : - dependency.requested.toString(), - packages[groupArtifact]?.specString, - conflictingByGroupArtifact[groupArtifact], - parents.collect { - PackageSpecifier.fromModuleComponentSelector( - it.requested as ModuleComponentSelector).specString - })) + // Dump the complete dependency graph and the conflict state of each node. + if (project.hasProperty("DUMP_DEBUG_GRAPH") && + project.getProperty("DUMP_DEBUG_GRAPH") == "1") { + summaryGraph.each { + DependencyResult dependency, List parents -> + String groupArtifact = + PackageSpecifier.fromModuleComponentSelector( + dependency.requested as ModuleComponentSelector).groupArtifactString + ResolvedComponentResult resolved = + (dependency instanceof ResolvedDependencyResult) ? + (dependency as ResolvedDependencyResult).selected : null + logger.quiet( + sprintf( + "%s* %s (new: %s, conflicting: %b (parents: %s))", + " ".multiply(parents.size()), + resolved && resolved.toString() != dependency.requested.toString() ? + sprintf("%s --> %s (%s)", dependency.requested.toString(), + resolved.toString(), resolved.selectionReason) : + dependency.requested.toString(), + packages[groupArtifact]?.specString, + conflictingByGroupArtifact[groupArtifact], + parents.collect { + PackageSpecifier.fromModuleComponentSelector( + it.requested as ModuleComponentSelector).specString + })) + } } logger.info(sprintf("=== Selected dependencies: %s", @@ -1204,6 +1554,8 @@ Tuple2> loosenVersionContraintsForConflicts( * * @param oldPackages Package specifications prior to the version change. * @param newPackages Package specifications after the version change. + * @param packageMapper Object that applied mapping to packages when they were + * resolved. * * @returns Map of [oldVersion, newVersion] tuples indexed by the group:artifact * of each package with a modified version. @@ -1211,15 +1563,28 @@ Tuple2> loosenVersionContraintsForConflicts( Map>> getModifiedPackageVersions( Iterable oldPackages, - Iterable newPackages) { + Iterable newPackages, PackageMapper packageMapper) { // Track package specifications that were modified. Map>> packagesModified = [:] // Bucket old and new versions by group artifact name. Map> oldPackagesByGroupArtifact = PackageSpecifier.byGroupArtifact(oldPackages) + // This performs the inverse mapping from each new package to the original + // package name so that it's possible to track the replacement. + // For example, if foo:bar:1.2.3 is mapped to foox:bish:1.0.0 this will + // generate the dictionary ["foo:bar", ["foox:bish:1.0.0"] so that when + // the code below looks for modifications to the package "foo:bar" it will + // map to "foox:bish". Map> newPackagesByGroupArtifact = - PackageSpecifier.byGroupArtifact(newPackages) + PackageSpecifier.byGroupArtifact(newPackages).collectEntries { + String groupArtifact, List pkgs -> + // There should only be *one* new version of the package selected. + assert pkgs.size() == 1 + PackageSpecifier oldPkg = packageMapper.inverseMap( + PackageSpecifier.fromString(pkgs[0].groupArtifactString)) + return [oldPkg.groupArtifactString, pkgs] + } oldPackagesByGroupArtifact.each { String groupArtifact, List oldItems -> @@ -1268,6 +1633,7 @@ Map, Configuration> resolveConflictingPackages( - Iterable packages) { + Iterable packages, PackageMapper packageMapper) { + // Substitute top level packages. + packages = packages.collect { packageMapper.map(it) } // Select the most recent package for each of the specified set of packages. Set currentPackages = PackageSpecifier.mostRecentByGroupArtifact(packages).values().toSet() - // Save the set of packages requested by the user. - Set initialPackages = currentPackages.clone().toSet() int resolutionAttempt = 1 Configuration userPackagesToQuery = null boolean resolutionComplete = false @@ -1290,16 +1656,21 @@ Tuple2, Configuration> resolveConflictingPackages( // Copy the configuration to query the set of required dependencies. userPackagesToQuery = project.configurations.create( "userPackagesToQuery" + resolutionAttempt.toString()) + packageMapper.applyToProjectConfiguration(userPackagesToQuery, project) // Add user specified packages to the userPackages configuration. // This allows us to resolve during the configuration phase. - logger.quiet(sprintf("Resolution attempt %d: packages %s", - resolutionAttempt, - PackageSpecifier.specStrings( - currentPackages).toString())) + if (!resolutionComplete) { + logger.quiet(sprintf("Resolution attempt %d: packages %s", + resolutionAttempt, + PackageSpecifier.specStrings( + currentPackages).toString())) + } PackageSpecifier.specStrings(currentPackages).each { project.dependencies.add(userPackagesToQuery.name, it) } + if (resolutionComplete) break + ResolutionResult resolutionResult = userPackagesToQuery.incoming.resolutionResult // Warn the user of missing dependencies. @@ -1311,6 +1682,7 @@ Tuple2, Configuration> resolveConflictingPackages( "to use %s, failed due to %s", it.requested, it.from, it.attempted, it.failure)) } + Set newPackages = currentPackages boolean conflictsFound = false (conflictsFound, newPackages) = loosenVersionContraintsForConflicts( @@ -1356,12 +1728,13 @@ Tuple2, Configuration> resolveConflictingPackages( * the missing packages. * * @param packages Package specifications to resolve. + * @param packageMapper Package mapping to apply on dependency resolution. * * @returns Package specifications with the @srcaar artifact specifier for each * artifact where a fallback package exists. */ Set fallbackToSrcAarArtifacts( - Iterable packages) { + Iterable packages, PackageMapper packageMapper) { // Search for all packages, excluding those with explicit artifact types and // find the missing set. Map searchPackages = @@ -1370,6 +1743,7 @@ Set fallbackToSrcAarArtifacts( } Configuration findMissingConfig = project.configurations.create("findMissing") + packageMapper.applyToProjectConfiguration(findMissingConfig, project) PackageSpecifier.specStrings(packages).each { project.dependencies.add(findMissingConfig.name, it) } @@ -1394,6 +1768,7 @@ Set fallbackToSrcAarArtifacts( // Search for missing packages using the srcaar artifact type. Configuration searchForSrcAarConfig = project.configurations.create("searchForSrcAars") + packageMapper.applyToProjectConfiguration(searchForSrcAarConfig, project) PackageSpecifier.specStrings(fallbackPackages.values()).each { project.dependencies.add(searchForSrcAarConfig.name, it) } @@ -1425,17 +1800,17 @@ def testGetComponentsFromPackage() { new PackageSpecifier(group: "org.test.psr", artifact: "something-neat", versionExpression: "1.2.3", - artifactType: null)], + artifactType: "")], ["org.test.psr:something-neat", new PackageSpecifier(group: "org.test.psr", artifact: "something-neat", - versionExpression: null, - artifactType: null)], + versionExpression: "", + artifactType: "")], ["org.test.psr", new PackageSpecifier(group: "org.test.psr", - artifact: null, - versionExpression: null, - artifactType: null)], + artifact: "", + versionExpression: "", + artifactType: "")], ["", new PackageSpecifier()]].each { String packageSpecifier, PackageSpecifier expected -> PackageSpecifier result = PackageSpecifier.fromString(packageSpecifier) @@ -1470,6 +1845,19 @@ def testPackageSpecifierStrings() { } } +def testPackageSpecifierVersionRange() { + [[PackageSpecifier.fromString("a-b:c-d:[1.2.3,4.5.6]"), "[1.2.3,4.5.6]"], + [PackageSpecifier.fromString("a-b:c-d:"), ""]].each { + pkg, expected -> + String result = pkg.versionRange.versionExpression + if (result != expected) { + throw new Exception( + sprintf("Invalid version range %s, expected %s for %s", + result, expected, pkg)) + } + } +} + def testGetMostRecentPackagesByGroupArtifact() { [[["a.b.c:d-e:1.2.3", "a.b.c:e-f:+"], ["a.b.c:d-e": "a.b.c:d-e:1.2.3", @@ -1658,9 +2046,11 @@ def testSortPackagesByVersion() { "a.b.c:e-f:1.0.0", "a.b.c:e-f:[0.5.2,3.2.0]", "a.b.c:e-f:2.1.+", + "a.b.c:e-f", "a.b.c:e-f:1.0.1", ] List expectedSortedByVersion = [ + "a.b.c:e-f", "a.b.c:e-f:1.0.0", "a.b.c:e-f:1.0.1", "a.b.c:e-f:2.1.+", @@ -1680,6 +2070,8 @@ def testSortPackagesByVersion() { } def testGetModifiedPackageVersions() { + PackageMapper packageMapper = + new PackageMapper(dependenciesMap: ["foo:bar": "foox:bish:1.0.0"]) List oldPackages = [ "a.b.c:e-f:1.2.3", "a.b.c:f-h:4.5.6", @@ -1690,14 +2082,16 @@ def testGetModifiedPackageVersions() { "z.z.z:x-y:5.0.1", "z.z.z:x-y:6.0.0", "com.android.support:appcompat-v7:23.0.+", + "foo:bar:1.2.3", + "foo:bar:2.0+", ] List newPackages = [ "a.b.c:e-f:1.3.0", "a.b.c:f-h:4.6.2", - "e.f.g:a-b:0.1.0", "e.f.g:a-b:[0.1.0]", "z.z.z:x-y:6.0.0", "com.android.support:appcompat-v7:23.0.1@aar", + "foox:bish:1.0.0", ] Map> expectedModifiedPackages = [ "a.b.c:e-f": [[PackageSpecifier.fromString("a.b.c:e-f:1.2.3"), @@ -1708,11 +2102,16 @@ def testGetModifiedPackageVersions() { PackageSpecifier.fromString("z.z.z:x-y:6.0.0")], [PackageSpecifier.fromString("z.z.z:x-y:5.0.1"), PackageSpecifier.fromString("z.z.z:x-y:6.0.0")]], + "foo:bar": [[PackageSpecifier.fromString("foo:bar:1.2.3"), + PackageSpecifier.fromString("foox:bish:1.0.0")], + [PackageSpecifier.fromString("foo:bar:2.0+"), + PackageSpecifier.fromString("foox:bish:1.0.0")]], ] Map>> modifiedPackages = getModifiedPackageVersions(PackageSpecifier.fromStrings(oldPackages), - PackageSpecifier.fromStrings(newPackages)) + PackageSpecifier.fromStrings(newPackages), + packageMapper) if (modifiedPackages != expectedModifiedPackages) { throw new Exception( sprintf("Invalid modified packages %s, expected %s for %s", @@ -1858,7 +2257,10 @@ def testVersionRangeMaximumVersionOfRange() { } def testVersionRangeVersionInRange() { - [[VersionRange.fromExpression("1.2.3+"), new Version("1.2.3"), true], + [[VersionRange.fromExpression(""), new Version(""), true], + [VersionRange.fromExpression(""), new Version("1.2.3"), true], + [VersionRange.fromExpression("1.2.3+"), new Version(), true], + [VersionRange.fromExpression("1.2.3+"), new Version("1.2.3"), true], [VersionRange.fromExpression("1.2.3+"), new Version("1.2.4"), true], [VersionRange.fromExpression("1.2.3+"), new Version("1.1.0"), false], [VersionRange.fromExpression("1.2.3"), new Version("1.2.3"), true], @@ -1940,6 +2342,58 @@ def testVersionRangeFromExpression() { } } +def testPackageMapperEmpty() { + PackageMapper packageMapper = new PackageMapper() + ["com.android.databinding:baseLibrary:3.4.0", + "com.android.support:appcompat-v7:26.0.1", + "com.android.support:leanback-v17:25.2.0", + "com.android.support:support-v4:25.2.0", + "androidx.legacy:legacy-support-v4:1.0.0"].each { String source -> + String expected = PackageSpecifier.fromString(source).groupArtifactString + PackageSpecifier pkg = packageMapper.map( + PackageSpecifier.fromString(expected)) + String value = pkg.specString + if (value != expected) { + throw new Exception(sprintf("Invalid mapped package %s vs %s for %s", + value, expected, source)) + } + String inverseSpec = packageMapper.inverseMap(pkg).specString + if (inverseSpec != expected) { + throw new Exception(sprintf("Invalid inverse mapped package %s vs %s " + + "for %s", inverseSpec, expected, source)) + } + } +} + +def testPackageMapperFromJetifierProcessor() { + PackageMapper packageMapper = PackageMapper.fromJetifierProcessor("3.4.0") + [["com.android.databinding:baseLibrary:3.4.0", + "com.android.databinding:baseLibrary:3.4.0"], + ["com.android.support:appcompat-v7:26.0.1", + "androidx.appcompat:appcompat:1.0.0"], + ["com.android.support:leanback-v17:25.2.0", + "androidx.leanback:leanback:1.0.0"], + ["com.android.support:support-v4:25.2.0", + "androidx.legacy:legacy-support-v4:1.0.0"]].each { String source, + String expected -> + PackageSpecifier pkg = packageMapper.map( + PackageSpecifier.fromString(source)) + String value = pkg.specString + if (value != expected) { + throw new Exception( + sprintf("Invalid mapped package %s vs %s for %s", + value, expected, source)) + } + expected = PackageSpecifier.fromString(source).groupArtifactString + String inverseSpec = packageMapper.inverseMap(pkg).specString + if (inverseSpec != expected) { + throw new Exception( + sprintf("Invalid inverse mapped package %s vs %s for %s (%s)", + inverseSpec, expected, value, source)) + } + } +} + // Run unit tests // TODO(b/79267099): Factor the tests out of this script. def runTests() { @@ -1950,6 +2404,7 @@ def runTests() { testVersionRangeVersionInRange() testGetComponentsFromPackage() testPackageSpecifierStrings() + testPackageSpecifierVersionRange() testSortPackagesByVersion() testMostRecentVersionLockedPackagesFromSet() testVersionLockPackages() @@ -1960,6 +2415,8 @@ def runTests() { testAddVersionLockedPackageToSet() testCreateVersionLockedSetBlacklist() testAddVersionLockedPackageToSetWithBlacklist() + testPackageMapperEmpty() + testPackageMapperFromJetifierProcessor() } // Configure project properties. @@ -2011,6 +2468,32 @@ project.ext { if (project.hasProperty("USE_REMOTE_MAVEN_REPOS")) { useRemoteMavenRepos = project.getProperty("USE_REMOTE_MAVEN_REPOS") == "1" } + boolean useJetifier = false + if (project.hasProperty("USE_JETIFIER")) { + useJetifier = project.getProperty("USE_JETIFIER") == "1" + } + String dataBindingVersion = "" + if (useJetifier) { + if (project.hasProperty("DATA_BINDING_VERSION")) { + dataBindingVersion = + project.getProperty("DATA_BINDING_VERSION") + } + if (!dataBindingVersion) { + println helpText + logger.error("Project property DATA_BINDING_VERSION must be " + + "specified if USE_JETIFIER is enabled.") + throw new InvalidUserDataException("Missing DATA_BINDING_VERSION") + } + } + // Create package mapper for Jetifier package substitutions. + PackageMapper packageMapper = useJetifier ? + PackageMapper.fromJetifierProcessor(dataBindingVersion) : + new PackageMapper() + // Apply package mapper to predeclared configurations. + [project.configurations.transitivePackagesConfig, + project.configurations.copyPackagesConfig].each { Configuration config -> + packageMapper.applyToProjectConfiguration(config, project) + } // Construct a list of local Maven URIs in the Android SDK. if (androidSdkRoot) { @@ -2021,7 +2504,7 @@ project.ext { } // Add Google maven repositories. if (useRemoteMavenRepos) { - mavenUris.push(new URI("https://maven.google.com/")) + mavenUris.push(project.project.repositories.google().url) } // List of URIs to add to the set of maven sources. @@ -2037,7 +2520,6 @@ project.ext { } if (useMavenLocalRepo) mavenLocal() if (useRemoteMavenRepos) { - jcenter() mavenCentral() } } @@ -2050,7 +2532,7 @@ project.ext { if (!project.hasProperty("PACKAGES_TO_COPY")) { println helpText logger.error("Project property PACKAGES_TO_COPY must be specified.") - System.exit(1) + throw new InvalidUserDataException("Missing PACKAGES_TO_COPY") } Set userSpecifiedPackages = PackageSpecifier.fromStrings( @@ -2063,7 +2545,7 @@ project.ext { if (!project.hasProperty("TARGET_DIR")) { println helpText logger.error("Project property TARGET_DIR must be specified.") - System.exit(1) + throw new InvalidUserDataException("Missing TARGET_DIR") } targetDir = project.getProperty("TARGET_DIR") if (targetDir && !(new File(targetDir)).isAbsolute()) { @@ -2075,14 +2557,15 @@ project.ext { // Fallback to search for srcaar artifacts when an artifact is missing. // We update the user specified package set here as this operation should be // transparent to the user. - userSpecifiedPackages = fallbackToSrcAarArtifacts(userSpecifiedPackages) + userSpecifiedPackages = fallbackToSrcAarArtifacts(userSpecifiedPackages, + packageMapper) // Resolve while searching for a set of non-conflicting package // specifications. Set resolvedConflictingPackages Configuration resolveConflictConfig (resolvedConflictingPackages, resolveConflictConfig) = - resolveConflictingPackages(userSpecifiedPackages) + resolveConflictingPackages(userSpecifiedPackages, packageMapper) Map resolvedConflictingPackagesByArtifactGroup = PackageSpecifier.mostRecentByGroupArtifact(resolvedConflictingPackages) @@ -2120,13 +2603,27 @@ project.ext { // Determine which, if any, package specs were modified. packagesModified = getModifiedPackageVersions(userSpecifiedPackages, - finalSelectedPackages.values()) + finalSelectedPackages.values(), + packageMapper) // Gather transitive dependencies of all selected packages. - PackageSpecifier.specStrings(finalSelectedPackages.values()).each { - project.dependencies.transitivePackagesConfig it + Map userSpecifiedPackagesByGroupArtifact = + PackageSpecifier.mostRecentByGroupArtifact(userSpecifiedPackages) + finalSelectedPackages.each { String pkgKey, PackageSpecifier selected -> + // Gradle doesn't resolve transitive dependencies for artifacts with a + // classifier https://github.com/gradle/gradle/issues/1487 so only use + // the artifact type it's specified directly by user or we're using a + // .srcaar artifact. + PackageSpecifier userPkg = userSpecifiedPackagesByGroupArtifact[pkgKey] + String specString = + ((userPkg && userPkg.artifactType) || selected.artifactType == "srcaar") ? + selected.specString : + (new PackageSpecifier( + group: selected.group, + artifact: selected.artifact, + versionExpression: selected.versionExpression)).specString + project.dependencies.transitivePackagesConfig specString } - packagesToCopy = (project.configurations.transitivePackagesConfig. resolvedConfiguration.lenientConfiguration. getArtifacts(Specs.satisfyAll()).findResults { @@ -2156,37 +2653,23 @@ project.ext { project.dependencies.copyPackagesConfig it } + // Create a package processor to generate processing tasks. + DefaultPackageProcessor packageProcessor = + useJetifier ? new JetpackPackageProcessor(dataBindingVersion) : + new DefaultPackageProcessor() // Generate tasks to copy each artifact to a unique filename in the target // directory. project.configurations.copyPackagesConfig. resolvedConfiguration.lenientConfiguration. getArtifacts(Specs.satisfyAll()).each { ResolvedArtifact artifact -> PackageSpecifier pkg = PackageSpecifier.fromResolvedArtifact(artifact) - String artifactTargetFilename = pkg.filename - // If the target file does not exist generate a task to copy it. - Task copyTask = tasks.create( - name: "copy_" + artifactTargetFilename, - type: Copy, - description: sprintf("Copy %s (%s) to %s", pkg.specString, - artifact.file, artifactTargetFilename)) - copyTask.with { - from artifact.file - into targetDir - rename( - // Rename the file to the target filename and log the copied file & - // artifact so that they can be summarized when all copy artifact - // tasks are complete. - { - String filename -> - copiedFileArtifacts.add( - new Tuple2( - new File(artifactTargetFilename), pkg)) - return artifactTargetFilename - } - ) - doFirst { logger.info(description) } - } - copyTasks.add(copyTask) + File artifactTargetFile = new File(targetDir, pkg.filename) + // If the target file does not exist, generate a task to copy or process it. + if (artifactTargetFile.exists()) return + + copyTasks.add(packageProcessor.createTask( + project, artifact, pkg, artifactTargetFile, + copiedFileArtifacts)) } } diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test.gradle b/source/AndroidResolver/scripts/download_artifacts_test.gradle similarity index 57% rename from source/PlayServicesResolver/scripts/download_artifacts_test.gradle rename to source/AndroidResolver/scripts/download_artifacts_test.gradle index 83c20495..02d8cd54 100644 --- a/source/PlayServicesResolver/scripts/download_artifacts_test.gradle +++ b/source/AndroidResolver/scripts/download_artifacts_test.gradle @@ -15,34 +15,54 @@ */ import java.security.MessageDigest +import java.io.BufferedInputStream +import java.io.FileInputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream // Logger which captures standard output or error streams to a buffer. class StandardOutputErrorLogger implements StandardOutputListener { // List of lines captured by this logger. - private def outputList = [] - - // Implements StandardOutputListener to capture a log message in - // outputList. + private List outputList = [] + + /* + * Implements StandardOutputListener to capture a log message in + * outputList. + * + * @param output Data to capture. + */ void onOutput(CharSequence output) { outputList.add(output) } - // Retrieve a string containing the lines aggregated by this logger. + /* + * Retrieve a string containing the lines aggregated by this logger. + * + * @returns All data aggregated by this logger. + */ String getOutput() { return outputList.join("") } - // Install the logger on the standard output and error streams of a task and - // clear the internal buffer. - def install(taskToLog) { + /* + * Install the logger on the standard output and error streams of a task and + * clear the internal buffer. + * + * @param taskToLog Task to install this logger on. + */ + void install(Task taskToLog) { outputList = [] taskToLog.logging.addStandardOutputListener(this) taskToLog.logging.addStandardErrorListener(this) } - // Remove the logger from the standard output and error streams of a task and - // clear the internal buffer. - def uninstall(taskToLog) { + /* + * Remove the logger from the standard output and error streams of a task and + * clear the internal buffer. + * + * @param taskToLog Task to remove this logger from. + */ + void uninstall(Task taskToLog) { taskToLog.logging.removeStandardOutputListener(this) taskToLog.logging.removeStandardErrorListener(this) } @@ -56,7 +76,8 @@ project.ext { outputDir = new File(System.getProperty("user.dir"), "download_artifacts_test_output") - // Gradle file under test. + // Gradle project under test. + srcSettingsFile = new File(scriptDirectory, "settings.gradle") srcBuildFile = new File(scriptDirectory, "download_artifacts.gradle") buildFile = new File(outputDir, srcBuildFile.name) @@ -79,14 +100,17 @@ project.ext { // Header in the download script's output that describes the set of artifacts // that were modified from what the user requested. modifiedArtifactsHeader = "Modified artifacts:" - - // Logger which captures the output of a task. - standardOutputErrorLogger = new StandardOutputErrorLogger() } -// Generate a task to create the specified directory File. -def createDirectoryTask(directoryFile) { - def createDirectory = tasks.create( +/* + * Generate a task to create the specified directory File. + * + * @param directoryFile Directory to create. + * + * @returns Task to create the specified directory. + */ +Task createDirectoryTask(File directoryFile) { + Task createDirectory = tasks.create( name: "create_" + directoryFile.path.replaceAll(/[\/\\:]/, "-"), description: "Creates the directory " + directoryFile.path) createDirectory.with { @@ -100,19 +124,13 @@ task copyTestScript( type: Copy, dependsOn: createDirectoryTask(project.ext.outputDir)) { description "Copy the test script into the test project" - from srcBuildFile + from srcBuildFile, srcSettingsFile into outputDir } -// Generate a task to create a test output directory. -def createTestDirectoryTask(directoryFile) { - def createDirectory = createDirectoryTask(directoryFile) - createDirectory.dependsOn copyTestScript - return createDirectory -} - // Create GradleBuild task StartParameter to execute download_artifacts.build. -def createStartParameters(packagesToCopy, targetDir) { +StartParameter createStartParameters(String packagesToCopy, File targetDir, + boolean useJetifier) { def startParameters = new StartParameter() startParameters.projectProperties = [ "ANDROID_HOME": project.ext.androidHome, @@ -124,14 +142,21 @@ def createStartParameters(packagesToCopy, targetDir) { // repos. "USE_MAVEN_LOCAL_REPO": "0", "USE_REMOTE_MAVEN_REPOS": "0", + "USE_JETIFIER": useJetifier ? "1" : "0", + "DATA_BINDING_VERSION": useJetifier ? "3.4.0" : "", ] startParameters.logLevel = logging.level return startParameters } -// Calculate the MD5 checksum of the specified file returning the checksum as -// a string. -def md5ChecksumFile(fileToRead) { +/* + * Calculate the MD5 checksum of the specified file. + * + * @param fileToRead File to calculate checksum from. + * + * @returns Checksum as a string. + */ +String md5ChecksumFile(File fileToRead) { MessageDigest digest = MessageDigest.getInstance("MD5") def contents = fileToRead.bytes digest.update(contents, 0, contents.length) @@ -140,20 +165,95 @@ def md5ChecksumFile(fileToRead) { return bigInt.toString(16).padLeft(32, '0') } -// Validate each target files for each source file yielded by the specified task -// are the same. If outputInputFileMap yields null for an output file, the -// file contents is not validated. -def validateFilesMatch(taskToValidate, outputInputFileMap) { - def mismatchingFiles = [] - taskToValidate.outputs.files.each { - def inputFile = outputInputFileMap[it] - if (inputFile != null && it.exists()) { - def inputFileChecksum = md5ChecksumFile(inputFile) - def outputFileChecksum = md5ChecksumFile(it) +/* + * Determine whether a filename is a zip file. + * + * @param fileObj File object to query. + * + * @returns true if the file is a zip file, false otherwise. + */ +boolean isZipFile(File fileObj) { + String filename = fileObj.name + return filename.endsWith(".zip") || + filename.endsWith(".jar") || + filename.endsWith(".aar") || + filename.endsWith(".srcaar") +} + +/* + * Reads all zip file entry metadata into a map. + * + * @param fileToRead File to read zip file entries from. + * + * @returns Map of entry filename to "size:crc32" strings. + */ +Map readZipEntries(File fileToRead) { + ZipInputStream inputStream = new ZipInputStream( + new BufferedInputStream(new FileInputStream(fileToRead))) + Map entries = [:] + while (true) { + ZipEntry entry = inputStream.nextEntry + if (entry == null) break + entries[entry.name] = sprintf("%ld:%ld", entry.size, entry.crc) + } + return entries +} + + +/* + * Validate the content of two zip files match. + * + * @param inputFile File with expected contents. + * @param outputFile File to compare with inputFile. + * + * @returns List of error messages if the content of the files do not match. + */ +List validateZipFilesMatch(File inputFile, File outputFile) { + Map inputEntries = readZipEntries(inputFile) + Map outputEntries = readZipEntries(outputFile) + List errors = [] + outputEntries.each { String entryName, String outputMetadata -> + String inputMetadata = inputEntries[entryName] + if (!inputMetadata) { + errors.add(sprintf("%s %s (%s) does not exist in %s", + outputFile.path, entryName, outputMetadata, + inputFile.path)) + } else if (inputMetadata != outputMetadata) { + errors.add(sprintf("%s %s (%s) != %s %s (%s)", + inputFile.path, entryName, inputMetadata, + outputFile.path, entryName, outputMetadata)) + } + } + return errors +} + +/* Compare files yield by a task match the expected input files. + * + * Validate each target files for each source file yielded by the specified task + * are the same. If outputInputFileMap yields null for an output file, the + * file contents is not validated. + * + * @param taskToValidate Task to retrieve the set of output files from. + * @param outputInputFileMap Map of output to input files for the task. + * + * @throws TaskExecutionException if the files don't match. + */ +void validateFilesMatch(Task taskToValidate, + Map outputInputFileMap) { + List mismatchingFiles = [] + taskToValidate.outputs.files.each { File outputFile + File inputFile = outputInputFileMap[outputFile] + if (inputFile != null && outputFile.exists()) { + String inputFileChecksum = md5ChecksumFile(inputFile) + String outputFileChecksum = md5ChecksumFile(outputFile) if (inputFileChecksum != outputFileChecksum) { - mismatchingFiles.add(sprintf("%s (%s) != %s (%s)", - inputFile.path, inputFileChecksum, - it.path, outputFileChecksum)) + if (isZipFile(inputFile) && isZipFile(outputFile)) { + mismatchingFiles += validateZipFilesMatch(inputFile, outputFile) + } else { + mismatchingFiles.add(sprintf("%s (%s) != %s (%s)", + inputFile.path, inputFileChecksum, + outputFile.path, outputFileChecksum)) + } } } } @@ -162,13 +262,18 @@ def validateFilesMatch(taskToValidate, outputInputFileMap) { taskToValidate, new Exception( sprintf("%s failed, unexpected output file(s)\n%s\n\n%s\n", taskToValidate.name, mismatchingFiles.join("\n"), - project.ext.standardOutputErrorLogger.output))) + taskToValidate.ext.standardOutputErrorLogger.output))) } } -// Validate all output files of a task exist. -def validateOutputFilesExist(taskToValidate) { - def missingFiles = [] +/* + * Validate all output files of a task exist. + * + * @param taskToValidate Task to check that all output files specified + * by the task are present. + */ +void validateOutputFilesExist(Task taskToValidate) { + List missingFiles = [] taskToValidate.outputs.files.each { if (!it.exists()) { missingFiles.add(it) } } @@ -177,19 +282,26 @@ def validateOutputFilesExist(taskToValidate) { taskToValidate, new Exception( sprintf("%s failed, missing expected file(s)\n%s\n\n%s\n", taskToValidate.name, missingFiles.join("\n"), - project.ext.standardOutputErrorLogger.output))) + taskToValidate.ext.standardOutputErrorLogger.output))) } } -// Split the output of the download script into sections. -def downloadScriptOutputToSectionsList(output) { - def validSections = [ +/* + * Split the output of the download script into sections. + * + * @param Output of the download_artifacts.gradle script. + * + * @returns Up to a list of 3 elements, for the sections + * "Copied artifacts:", "Missing artifacts:", "Modified artifacts:". + */ +List downloadScriptOutputToSectionsList(String output) { + Set validSections = [ "Copied artifacts:", "Missing artifacts:", "Modified artifacts:", ].toSet() - def sections = [] - def currentSection = [] + List sections = [] + ListcurrentSection = [] // Adds the current section to the list of sections and flushes the list. def endSectionClosure = { if (currentSection) { @@ -211,49 +323,71 @@ def downloadScriptOutputToSectionsList(output) { return sections } -// Generates a test case that: -// * Attempts to download a set of artifacts specified by packageSpecification -// into a target directory (derived from taskName) using the downloader -// script. -// * Validates the expected artifacts are downloaded and match the specified -// set of source files specified by outputInputFileMap. -// * Validates the script output matches expectedScriptOutput (list of strings where -// each element is a section of the script's parsed output). -def createTestTask(taskName, taskDescription, packageSpecification, - outputInputFileMap, expectedScriptOutput, iterations=1) { +/* + * Generates a test case that: + * * Attempts to download a set of artifacts specified by packageSpecification + * into a target directory (derived from taskName) using the downloader + * script. + * * Validates the expected artifacts are downloaded and match the specified + * set of source files specified by outputInputFileMap. + * * Validates the script output matches expectedScriptOutput (list of strings where + * each element is a section of the script's parsed output). + * + * @param taskName Name of the task to create. + * @param taskDescription Verbose description of the task. + * @param outputInputFileMap Map of output files to expected (input) files. + * @param expectedScriptOutput List of 3 sections (copied, missing, modified) + * which match the output of the script. + * See downloadScriptOutputToSectionsList() + * @param iterations Number of times to run the test task. + * @param useJetifier Whether to use the Jetifier to substitute and rewrite + * libraries. + */ +void createTestTask(String taskName, String taskDescription, + String packageSpecification, + Map outputInputFileMap, + List expectedScriptOutput, + int iterations=1, boolean useJetifier=false) { // Create a target directory relative to the output directory. def targetDirFile = new File(project.ext.outputDir, taskName) // Move output file paths relative to the target directory and input paths // relative to the local maven repo. - def movedOutputInputFileMap = [:] + Map movedOutputInputFileMap = [:] outputInputFileMap.each { outputFile, inputFile -> movedOutputInputFileMap[new File(targetDirFile, outputFile)] = inputFile != null ? new File(project.ext.mavenRepo, inputFile) : null } - def createDirectoryTask = createTestDirectoryTask(targetDirFile) + Task createDirectoryTask = createDirectoryTask(targetDirFile) + createDirectoryTask.dependsOn copyTestScript iterations.times { - def currentIteration = it + 1 - def currentTaskName = taskName + (iterations > 1 ? "$currentIteration" : "") - def testTask = tasks.create(name: currentTaskName, - description: taskDescription, - type: GradleBuild, - dependsOn: createDirectoryTask) + int currentIteration = it + 1 + String currentTaskName = + taskName + (iterations > 1 ? "$currentIteration" : "") + Task testTask = tasks.create(name: currentTaskName, + description: taskDescription, + type: GradleBuild, + dependsOn: createDirectoryTask) testTask.with { + // Logger which captures the output of a task. + // This doesn't work in parallel builds at the moment. + // https://github.com/gradle/gradle/issues/6068 + ext.standardOutputErrorLogger = new StandardOutputErrorLogger() + outputs.files movedOutputInputFileMap.keySet() startParameter createStartParameters(packageSpecification, - targetDirFile) + targetDirFile, useJetifier) buildFile project.ext.buildFile dir project.ext.outputDir - doFirst { project.ext.standardOutputErrorLogger.install(it) } + doFirst { ext.standardOutputErrorLogger.install(it) } doLast { - project.ext.standardOutputErrorLogger.uninstall(it) + ext.standardOutputErrorLogger.uninstall(it) validateOutputFilesExist(it) validateFilesMatch(it, movedOutputInputFileMap) if (expectedScriptOutput != null) { - assert downloadScriptOutputToSectionsList( - project.ext.standardOutputErrorLogger.output) == - expectedScriptOutput + List parsedOutput = downloadScriptOutputToSectionsList( + ext.standardOutputErrorLogger.output) + assert parsedOutput == expectedScriptOutput } } } @@ -284,6 +418,21 @@ createTestTask( ["Copied artifacts:\n" + "com.android.support.support-annotations-23.0.1.magic"]) + createTestTask( + "testDownloadAvailableWithClassifier", + "Downloads artifacts with one of them having a classifier in the name.", + "org.test.psr:classifier:1.0.1:foo@aar;" + + "com.android.support:support-annotations:26.1.0;", + ["org.test.psr.classifier-1.0.1-foo.aar": + "org/test/psr/classifier/1.0.1/" + + "org.test.psr.classifier-1.0.1-foo.aar", + "com.android.support.support-annotations-26.1.0.jar": + "com/android/support/support-annotations/26.1.0/" + + "support-annotations-26.1.0.jar"], + ["Copied artifacts:\n" + + "com.android.support.support-annotations-26.1.0.jar\n" + + "org.test.psr.classifier-1.0.1-foo.aar"]) + createTestTask( "testDownloadAvailableTwice", "Downloads a single artifact and it's dependencies from maven.", @@ -319,7 +468,7 @@ createTestTask( "testDownloadUnavailable", "Attempts to download a non-existant artifact.", "apackage.thatdoes:notexist:9.9.9", - [], + [:], ["Missing artifacts:\n" + "apackage.thatdoes:notexist:+", "Modified artifacts:\n" + @@ -382,6 +531,21 @@ createTestTask( "com.google.android.gms.play-services-basement-9.8.0.aar\n" + "com.google.firebase.firebase-app-unity-4.3.0.aar"]) +createTestTask( + "testAndroidSupportMultidexNotVersionLocked", + "Ensure com.android.support:multidex isn't version locked to other legacy " + + "Android support libraries", + "com.android.support:multidex:1.0.3;" + + "com.android.support:support-annotations:24.0.0", + ["com.android.support.multidex-1.0.3.aar": + "com/android/support/multidex/1.0.3/multidex-1.0.3.aar", + "com.android.support.support-annotations-24.0.0.jar": + "com/android/support/support-annotations/24.0.0/" + + "support-annotations-24.0.0.jar"], + ["Copied artifacts:\n" + + "com.android.support.multidex-1.0.3.aar\n" + + "com.android.support.support-annotations-24.0.0.jar"]) + createTestTask( "testDownloadUsingVersionWildcard", "Download an artifact using a version wildcard.", @@ -524,7 +688,7 @@ createTestTask( "packages ordering should not change the resolution result.", "com.android.support:support-annotations:23.+;" + "com.android.support:support-annotations:24.+", - [], + [:], ["Copied artifacts:\n" + "com.android.support.support-annotations-24.0.0.jar"]) @@ -534,7 +698,7 @@ createTestTask( "packages ordering should not change the resolution result.", "com.android.support:support-annotations:24.+;" + "com.android.support:support-annotations:23.+", - [], + [:], ["Copied artifacts:\n" + "com.android.support.support-annotations-24.0.0.jar"]) @@ -617,6 +781,132 @@ createTestTask( "com.google.android.gms:play-services-tasks:12.0.0 --> " + "com.google.android.gms:play-services-tasks:+"]) +createTestTask( + "testDirectJetifier", + "Verify a direct dependency upon the legacy Android support library is " + + "remapped ot the Jetpack libraries by the Jetifier.", + "com.android.support:support-annotations:26.1.0", + ["androidx.annotation.annotation-1.0.0.jar": + "androidx/annotation/annotation/1.0.0/annotation-1.0.0.jar"], + ["Copied artifacts:\n" + + "androidx.annotation.annotation-1.0.0.jar", + "Modified artifacts:\n" + + "com.android.support:support-annotations:26.1.0 --> " + + "androidx.annotation:annotation:1.0.0"], + 1 /* iterations */, + true /* useJetifier */) + +createTestTask( + "testTransitiveJetifier", + "Verify transitive dependencies of a package are remapped with the " + + "Jetifier and the package referencing the legacy support libraries is " + + "processed by the Jetifier to reference Jetpack libraries.", + "com.google.android.gms:play-services-basement:9.8.0", + ["androidx.annotation.annotation-1.0.0.jar": + "androidx/annotation/annotation/1.0.0/annotation-1.0.0.jar", + "androidx.arch.core.core-common-2.0.0.jar": + "androidx/arch/core/core-common/2.0.0/core-common-2.0.0.jar", + "androidx.arch.core.core-runtime-2.0.0.aar": + "androidx/arch/core/core-runtime/2.0.0/core-runtime-2.0.0.aar", + "androidx.asynclayoutinflater.asynclayoutinflater-1.0.0.aar": + "androidx/asynclayoutinflater/asynclayoutinflater/1.0.0/" + + "asynclayoutinflater-1.0.0.aar", + "androidx.collection.collection-1.0.0.jar": + "androidx/collection/collection/1.0.0/collection-1.0.0.jar", + "androidx.coordinatorlayout.coordinatorlayout-1.0.0.aar": + "androidx/coordinatorlayout/coordinatorlayout/1.0.0/" + + "coordinatorlayout-1.0.0.aar", + "androidx.core.core-1.0.0.aar": + "androidx/core/core/1.0.0/core-1.0.0.aar", + "androidx.cursoradapter.cursoradapter-1.0.0.aar": + "androidx/cursoradapter/cursoradapter/1.0.0/cursoradapter-1.0.0.aar", + "androidx.customview.customview-1.0.0.aar": + "androidx/customview/customview/1.0.0/customview-1.0.0.aar", + "androidx.documentfile.documentfile-1.0.0.aar": + "androidx/documentfile/documentfile/1.0.0/documentfile-1.0.0.aar", + "androidx.drawerlayout.drawerlayout-1.0.0.aar": + "androidx/drawerlayout/drawerlayout/1.0.0/drawerlayout-1.0.0.aar", + "androidx.fragment.fragment-1.0.0.aar": + "androidx/fragment/fragment/1.0.0/fragment-1.0.0.aar", + "androidx.interpolator.interpolator-1.0.0.aar": + "androidx/interpolator/interpolator/1.0.0/interpolator-1.0.0.aar", + "androidx.legacy.legacy-support-core-ui-1.0.0.aar": + "androidx/legacy/legacy-support-core-ui//1.0.0/" + + "legacy-support-core-ui-1.0.0.aar", + "androidx.legacy.legacy-support-core-utils-1.0.0.aar": + "androidx/legacy/legacy-support-core-utils//1.0.0/" + + "legacy-support-core-utils-1.0.0.aar", + "androidx.legacy.legacy-support-v4-1.0.0.aar": + "androidx/legacy/legacy-support-v4/1.0.0/legacy-support-v4-1.0.0.aar", + "androidx.lifecycle.lifecycle-common-2.0.0.jar": + "androidx/lifecycle/lifecycle-common/2.0.0/lifecycle-common-2.0.0.jar", + "androidx.lifecycle.lifecycle-livedata-2.0.0.aar": + "androidx/lifecycle/lifecycle-livedata/2.0.0/lifecycle-livedata-2.0.0.aar", + "androidx.lifecycle.lifecycle-livedata-core-2.0.0.aar": + "androidx/lifecycle/lifecycle-livedata-core/2.0.0/" + + "lifecycle-livedata-core-2.0.0.aar", + "androidx.lifecycle.lifecycle-runtime-2.0.0.aar": + "androidx/lifecycle/lifecycle-runtime/2.0.0/lifecycle-runtime-2.0.0.aar", + "androidx.lifecycle.lifecycle-viewmodel-2.0.0.aar": + "androidx/lifecycle/lifecycle-viewmodel/2.0.0/lifecycle-viewmodel-2.0.0.aar", + "androidx.loader.loader-1.0.0.aar": + "androidx/loader/loader/1.0.0/loader-1.0.0.aar", + "androidx.localbroadcastmanager.localbroadcastmanager-1.0.0.aar": + "androidx/localbroadcastmanager/localbroadcastmanager/1.0.0/" + + "localbroadcastmanager-1.0.0.aar", + "androidx.media.media-1.0.0.aar": + "androidx/media/media/1.0.0/media-1.0.0.aar", + "androidx.print.print-1.0.0.aar": + "androidx/print/print/1.0.0/print-1.0.0.aar", + "androidx.slidingpanelayout.slidingpanelayout-1.0.0.aar": + "androidx/slidingpanelayout/slidingpanelayout/1.0.0/" + + "slidingpanelayout-1.0.0.aar", + "androidx.swiperefreshlayout.swiperefreshlayout-1.0.0.aar": + "androidx/swiperefreshlayout/swiperefreshlayout/1.0.0/" + + "swiperefreshlayout-1.0.0.aar", + "androidx.versionedparcelable.versionedparcelable-1.0.0.aar": + "androidx/versionedparcelable/versionedparcelable/1.0.0/" + + "versionedparcelable-1.0.0.aar", + "androidx.viewpager.viewpager-1.0.0.aar": + "androidx/viewpager/viewpager/1.0.0/viewpager-1.0.0.aar", + "com.google.android.gms.play-services-basement-9.8.0.aar": + "com/google/android/gms/play-services-basement/9.8.0-jetified/" + + "com.google.android.gms.play-services-basement-9.8.0.aar", + ], + ["Copied artifacts:\n" + + "androidx.annotation.annotation-1.0.0.jar\n" + + "androidx.arch.core.core-common-2.0.0.jar\n" + + "androidx.arch.core.core-runtime-2.0.0.aar\n" + + "androidx.asynclayoutinflater.asynclayoutinflater-1.0.0.aar\n" + + "androidx.collection.collection-1.0.0.jar\n" + + "androidx.coordinatorlayout.coordinatorlayout-1.0.0.aar\n" + + "androidx.core.core-1.0.0.aar\n" + + "androidx.cursoradapter.cursoradapter-1.0.0.aar\n" + + "androidx.customview.customview-1.0.0.aar\n" + + "androidx.documentfile.documentfile-1.0.0.aar\n" + + "androidx.drawerlayout.drawerlayout-1.0.0.aar\n" + + "androidx.fragment.fragment-1.0.0.aar\n" + + "androidx.interpolator.interpolator-1.0.0.aar\n" + + "androidx.legacy.legacy-support-core-ui-1.0.0.aar\n" + + "androidx.legacy.legacy-support-core-utils-1.0.0.aar\n" + + "androidx.legacy.legacy-support-v4-1.0.0.aar\n" + + "androidx.lifecycle.lifecycle-common-2.0.0.jar\n" + + "androidx.lifecycle.lifecycle-livedata-2.0.0.aar\n" + + "androidx.lifecycle.lifecycle-livedata-core-2.0.0.aar\n" + + "androidx.lifecycle.lifecycle-runtime-2.0.0.aar\n" + + "androidx.lifecycle.lifecycle-viewmodel-2.0.0.aar\n" + + "androidx.loader.loader-1.0.0.aar\n" + + "androidx.localbroadcastmanager.localbroadcastmanager-1.0.0.aar\n" + + "androidx.media.media-1.0.0.aar\n" + + "androidx.print.print-1.0.0.aar\n" + + "androidx.slidingpanelayout.slidingpanelayout-1.0.0.aar\n" + + "androidx.swiperefreshlayout.swiperefreshlayout-1.0.0.aar\n" + + "androidx.versionedparcelable.versionedparcelable-1.0.0.aar\n" + + "androidx.viewpager.viewpager-1.0.0.aar\n" + + "com.google.android.gms.play-services-basement-9.8.0.aar"], + 1 /* iterations */, + true /* useJetifier */) + task testUnitTests(type: GradleBuild, dependsOn: copyTestScript) { def startParameters = new StartParameter() startParameters.projectProperties = ["RUN_TESTS": "1"] @@ -629,4 +919,18 @@ task testUnitTests(type: GradleBuild, dependsOn: copyTestScript) { dir project.ext.outputDir } -project.defaultTasks = project.ext.testTaskNames +// Due to https://github.com/gradle/gradle/issues/6068 all test tasks +// must be run in serial at the moment so the following serializes all +// tasks. +// When the bug in Gradle is fixed the following code can be replaced with: +// project.defaultTasks = project.ext.testTaskNames +ext.testTaskNames.eachWithIndex { String taskName, int index -> + if (index == 0) return + project.getTasksByName(ext.testTaskNames[index - 1], false).each { + Task previousTask -> + project.getTasksByName(taskName, false).each { Task currentTask -> + previousTask.dependsOn(currentTask) + } + } +} +project.defaultTasks = [ext.testTaskNames[0]] diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.jar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.jar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.pom diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/annotation/annotation/1.0.0/annotation-1.0.0.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/annotation/annotation/1.0.0/annotation-1.0.0.jar new file mode 100644 index 00000000..124f128d Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/annotation/annotation/1.0.0/annotation-1.0.0.jar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/annotation/annotation/1.0.0/annotation-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/annotation/annotation/1.0.0/annotation-1.0.0.pom new file mode 100644 index 00000000..a838662e --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/annotation/annotation/1.0.0/annotation-1.0.0.pom @@ -0,0 +1,28 @@ + + + 4.0.0 + androidx.annotation + annotation + 1.0.0 + Android Support Library Annotations + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. + http://developer.android.com/tools/extras/support-library.html + 2013 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-common/2.0.0/core-common-2.0.0.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-common/2.0.0/core-common-2.0.0.jar new file mode 100644 index 00000000..98ec8865 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-common/2.0.0/core-common-2.0.0.jar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-common/2.0.0/core-common-2.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-common/2.0.0/core-common-2.0.0.pom new file mode 100644 index 00000000..26d87c19 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-common/2.0.0/core-common-2.0.0.pom @@ -0,0 +1,48 @@ + + + 4.0.0 + androidx.arch.core + core-common + 2.0.0 + Android Arch-Common + Android Arch-Common + https://developer.android.com/topic/libraries/architecture/index.html + 2017 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + junit + junit + 4.12 + test + + + org.mockito + mockito-core + 2.19.0 + test + + + androidx.annotation + annotation + 1.0.0 + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-runtime/2.0.0/core-runtime-2.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-runtime/2.0.0/core-runtime-2.0.0.aar new file mode 100644 index 00000000..f876595c Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-runtime/2.0.0/core-runtime-2.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-runtime/2.0.0/core-runtime-2.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-runtime/2.0.0/core-runtime-2.0.0.pom new file mode 100644 index 00000000..432bf417 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/arch/core/core-runtime/2.0.0/core-runtime-2.0.0.pom @@ -0,0 +1,43 @@ + + + 4.0.0 + androidx.arch.core + core-runtime + 2.0.0 + aar + Android Arch-Runtime + Android Arch-Runtime + https://developer.android.com/topic/libraries/architecture/index.html + 2017 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.arch.core + core-common + 2.0.0 + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/asynclayoutinflater/asynclayoutinflater/1.0.0/asynclayoutinflater-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/asynclayoutinflater/asynclayoutinflater/1.0.0/asynclayoutinflater-1.0.0.aar new file mode 100644 index 00000000..337f4c49 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/asynclayoutinflater/asynclayoutinflater/1.0.0/asynclayoutinflater-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/asynclayoutinflater/asynclayoutinflater/1.0.0/asynclayoutinflater-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/asynclayoutinflater/asynclayoutinflater/1.0.0/asynclayoutinflater-1.0.0.pom new file mode 100644 index 00000000..b0ce2d0f --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/asynclayoutinflater/asynclayoutinflater/1.0.0/asynclayoutinflater-1.0.0.pom @@ -0,0 +1,44 @@ + + + 4.0.0 + androidx.asynclayoutinflater + asynclayoutinflater + 1.0.0 + aar + Android Support Library Async Layout Inflater + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2018 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.core + core + 1.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/collection/collection/1.0.0/collection-1.0.0.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/collection/collection/1.0.0/collection-1.0.0.jar new file mode 100644 index 00000000..78ac06c4 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/collection/collection/1.0.0/collection-1.0.0.jar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/collection/collection/1.0.0/collection-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/collection/collection/1.0.0/collection-1.0.0.pom new file mode 100644 index 00000000..45339bd7 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/collection/collection/1.0.0/collection-1.0.0.pom @@ -0,0 +1,42 @@ + + + 4.0.0 + androidx.collection + collection + 1.0.0 + Android Support Library collections + Standalone efficient collections. + http://developer.android.com/tools/extras/support-library.html + 2018 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + junit + junit + 4.12 + test + + + androidx.annotation + annotation + 1.0.0 + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/coordinatorlayout/coordinatorlayout/1.0.0/coordinatorlayout-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/coordinatorlayout/coordinatorlayout/1.0.0/coordinatorlayout-1.0.0.aar new file mode 100644 index 00000000..de447ec4 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/coordinatorlayout/coordinatorlayout/1.0.0/coordinatorlayout-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/coordinatorlayout/coordinatorlayout/1.0.0/coordinatorlayout-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/coordinatorlayout/coordinatorlayout/1.0.0/coordinatorlayout-1.0.0.pom new file mode 100644 index 00000000..83e81b86 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/coordinatorlayout/coordinatorlayout/1.0.0/coordinatorlayout-1.0.0.pom @@ -0,0 +1,51 @@ + + + 4.0.0 + androidx.coordinatorlayout + coordinatorlayout + 1.0.0 + aar + Android Support Library Coordinator Layout + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2011 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.core + core + 1.0.0 + aar + compile + + + androidx.customview + customview + 1.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/core/core/1.0.0/core-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/core/core/1.0.0/core-1.0.0.aar new file mode 100644 index 00000000..fea6bd3e Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/core/core/1.0.0/core-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/core/core/1.0.0/core-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/core/core/1.0.0/core-1.0.0.pom new file mode 100644 index 00000000..49b0fe81 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/core/core/1.0.0/core-1.0.0.pom @@ -0,0 +1,57 @@ + + + 4.0.0 + androidx.core + core + 1.0.0 + aar + Android Support Library compat + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2015 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.collection + collection + 1.0.0 + compile + + + androidx.lifecycle + lifecycle-runtime + 2.0.0 + aar + compile + + + androidx.versionedparcelable + versionedparcelable + 1.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/cursoradapter/cursoradapter/1.0.0/cursoradapter-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/cursoradapter/cursoradapter/1.0.0/cursoradapter-1.0.0.aar new file mode 100644 index 00000000..cd1494a9 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/cursoradapter/cursoradapter/1.0.0/cursoradapter-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/cursoradapter/cursoradapter/1.0.0/cursoradapter-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/cursoradapter/cursoradapter/1.0.0/cursoradapter-1.0.0.pom new file mode 100644 index 00000000..ffca433f --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/cursoradapter/cursoradapter/1.0.0/cursoradapter-1.0.0.pom @@ -0,0 +1,37 @@ + + + 4.0.0 + androidx.cursoradapter + cursoradapter + 1.0.0 + aar + Android Support Library Cursor Adapter + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2018 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/customview/customview/1.0.0/customview-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/customview/customview/1.0.0/customview-1.0.0.aar new file mode 100644 index 00000000..73e70ac4 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/customview/customview/1.0.0/customview-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/customview/customview/1.0.0/customview-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/customview/customview/1.0.0/customview-1.0.0.pom new file mode 100644 index 00000000..db3906ef --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/customview/customview/1.0.0/customview-1.0.0.pom @@ -0,0 +1,44 @@ + + + 4.0.0 + androidx.customview + customview + 1.0.0 + aar + Android Support Library Custom View + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2018 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.core + core + 1.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/documentfile/documentfile/1.0.0/documentfile-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/documentfile/documentfile/1.0.0/documentfile-1.0.0.aar new file mode 100644 index 00000000..79fd5502 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/documentfile/documentfile/1.0.0/documentfile-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/documentfile/documentfile/1.0.0/documentfile-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/documentfile/documentfile/1.0.0/documentfile-1.0.0.pom new file mode 100644 index 00000000..dfc9aadd --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/documentfile/documentfile/1.0.0/documentfile-1.0.0.pom @@ -0,0 +1,37 @@ + + + 4.0.0 + androidx.documentfile + documentfile + 1.0.0 + aar + Android Support Library Document File + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2018 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/drawerlayout/drawerlayout/1.0.0/drawerlayout-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/drawerlayout/drawerlayout/1.0.0/drawerlayout-1.0.0.aar new file mode 100644 index 00000000..a9968c7f Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/drawerlayout/drawerlayout/1.0.0/drawerlayout-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/drawerlayout/drawerlayout/1.0.0/drawerlayout-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/drawerlayout/drawerlayout/1.0.0/drawerlayout-1.0.0.pom new file mode 100644 index 00000000..507479bc --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/drawerlayout/drawerlayout/1.0.0/drawerlayout-1.0.0.pom @@ -0,0 +1,51 @@ + + + 4.0.0 + androidx.drawerlayout + drawerlayout + 1.0.0 + aar + Android Support Library Drawer Layout + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2018 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.core + core + 1.0.0 + aar + compile + + + androidx.customview + customview + 1.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/fragment/fragment/1.0.0/fragment-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/fragment/fragment/1.0.0/fragment-1.0.0.aar new file mode 100644 index 00000000..7a5c3605 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/fragment/fragment/1.0.0/fragment-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/fragment/fragment/1.0.0/fragment-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/fragment/fragment/1.0.0/fragment-1.0.0.pom new file mode 100644 index 00000000..e76f189a --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/fragment/fragment/1.0.0/fragment-1.0.0.pom @@ -0,0 +1,72 @@ + + + 4.0.0 + androidx.fragment + fragment + 1.0.0 + aar + Android Support Library fragment + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2011 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.core + core + 1.0.0 + aar + compile + + + androidx.legacy + legacy-support-core-ui + 1.0.0 + aar + compile + + + androidx.legacy + legacy-support-core-utils + 1.0.0 + aar + compile + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.loader + loader + 1.0.0 + aar + compile + + + androidx.lifecycle + lifecycle-viewmodel + 2.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/interpolator/interpolator/1.0.0/interpolator-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/interpolator/interpolator/1.0.0/interpolator-1.0.0.aar new file mode 100644 index 00000000..bccf86f7 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/interpolator/interpolator/1.0.0/interpolator-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/interpolator/interpolator/1.0.0/interpolator-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/interpolator/interpolator/1.0.0/interpolator-1.0.0.pom new file mode 100644 index 00000000..49a66396 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/interpolator/interpolator/1.0.0/interpolator-1.0.0.pom @@ -0,0 +1,37 @@ + + + 4.0.0 + androidx.interpolator + interpolator + 1.0.0 + aar + Android Support Library Interpolators + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2018 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-ui/1.0.0/legacy-support-core-ui-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-ui/1.0.0/legacy-support-core-ui-1.0.0.aar new file mode 100644 index 00000000..01275eb2 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-ui/1.0.0/legacy-support-core-ui-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-ui/1.0.0/legacy-support-core-ui-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-ui/1.0.0/legacy-support-core-ui-1.0.0.pom new file mode 100644 index 00000000..8a0cb3bc --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-ui/1.0.0/legacy-support-core-ui-1.0.0.pom @@ -0,0 +1,114 @@ + + + 4.0.0 + androidx.legacy + legacy-support-core-ui + 1.0.0 + aar + Android Support Library core UI + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2011 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.core + core + 1.0.0 + aar + compile + + + androidx.legacy + legacy-support-core-utils + 1.0.0 + aar + compile + + + androidx.customview + customview + 1.0.0 + aar + compile + + + androidx.viewpager + viewpager + 1.0.0 + aar + compile + + + androidx.coordinatorlayout + coordinatorlayout + 1.0.0 + aar + compile + + + androidx.drawerlayout + drawerlayout + 1.0.0 + aar + compile + + + androidx.slidingpanelayout + slidingpanelayout + 1.0.0 + aar + compile + + + androidx.interpolator + interpolator + 1.0.0 + aar + compile + + + androidx.swiperefreshlayout + swiperefreshlayout + 1.0.0 + aar + compile + + + androidx.asynclayoutinflater + asynclayoutinflater + 1.0.0 + aar + compile + + + androidx.cursoradapter + cursoradapter + 1.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-utils/1.0.0/legacy-support-core-utils-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-utils/1.0.0/legacy-support-core-utils-1.0.0.aar new file mode 100644 index 00000000..2980f603 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-utils/1.0.0/legacy-support-core-utils-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-utils/1.0.0/legacy-support-core-utils-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-utils/1.0.0/legacy-support-core-utils-1.0.0.pom new file mode 100644 index 00000000..39f3ee8f --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-core-utils/1.0.0/legacy-support-core-utils-1.0.0.pom @@ -0,0 +1,72 @@ + + + 4.0.0 + androidx.legacy + legacy-support-core-utils + 1.0.0 + aar + Android Support Library core utils + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2011 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.core + core + 1.0.0 + aar + compile + + + androidx.documentfile + documentfile + 1.0.0 + aar + compile + + + androidx.loader + loader + 1.0.0 + aar + compile + + + androidx.localbroadcastmanager + localbroadcastmanager + 1.0.0 + aar + compile + + + androidx.print + print + 1.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-v4/1.0.0/legacy-support-v4-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-v4/1.0.0/legacy-support-v4-1.0.0.aar new file mode 100644 index 00000000..bc64a974 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-v4/1.0.0/legacy-support-v4-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-v4/1.0.0/legacy-support-v4-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-v4/1.0.0/legacy-support-v4-1.0.0.pom new file mode 100644 index 00000000..8023e94a --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/legacy/legacy-support-v4/1.0.0/legacy-support-v4-1.0.0.pom @@ -0,0 +1,66 @@ + + + 4.0.0 + androidx.legacy + legacy-support-v4 + 1.0.0 + aar + Android Support Library v4 + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2011 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.core + core + 1.0.0 + aar + compile + + + androidx.media + media + 1.0.0 + aar + compile + + + androidx.legacy + legacy-support-core-utils + 1.0.0 + aar + compile + + + androidx.legacy + legacy-support-core-ui + 1.0.0 + aar + compile + + + androidx.fragment + fragment + 1.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-common/2.0.0/lifecycle-common-2.0.0.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-common/2.0.0/lifecycle-common-2.0.0.jar new file mode 100644 index 00000000..6c3f095c Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-common/2.0.0/lifecycle-common-2.0.0.jar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-common/2.0.0/lifecycle-common-2.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-common/2.0.0/lifecycle-common-2.0.0.pom new file mode 100644 index 00000000..6a63e5b7 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-common/2.0.0/lifecycle-common-2.0.0.pom @@ -0,0 +1,48 @@ + + + 4.0.0 + androidx.lifecycle + lifecycle-common + 2.0.0 + Android Lifecycle-Common + Android Lifecycle-Common + https://developer.android.com/topic/libraries/architecture/index.html + 2017 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + junit + junit + 4.12 + test + + + org.mockito + mockito-core + 2.19.0 + test + + + androidx.annotation + annotation + 1.0.0 + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata-core/2.0.0/lifecycle-livedata-core-2.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata-core/2.0.0/lifecycle-livedata-core-2.0.0.aar new file mode 100644 index 00000000..5583b9f5 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata-core/2.0.0/lifecycle-livedata-core-2.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata-core/2.0.0/lifecycle-livedata-core-2.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata-core/2.0.0/lifecycle-livedata-core-2.0.0.pom new file mode 100644 index 00000000..be468bea --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata-core/2.0.0/lifecycle-livedata-core-2.0.0.pom @@ -0,0 +1,50 @@ + + + 4.0.0 + androidx.lifecycle + lifecycle-livedata-core + 2.0.0 + aar + Android Lifecycle LiveData Core + Android Lifecycle LiveData Core + https://developer.android.com/topic/libraries/architecture/index.html + 2017 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.lifecycle + lifecycle-common + 2.0.0 + compile + + + androidx.arch.core + core-common + 2.0.0 + compile + + + androidx.arch.core + core-runtime + 2.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata/2.0.0/lifecycle-livedata-2.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata/2.0.0/lifecycle-livedata-2.0.0.aar new file mode 100644 index 00000000..27b091c1 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata/2.0.0/lifecycle-livedata-2.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata/2.0.0/lifecycle-livedata-2.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata/2.0.0/lifecycle-livedata-2.0.0.pom new file mode 100644 index 00000000..8b385013 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-livedata/2.0.0/lifecycle-livedata-2.0.0.pom @@ -0,0 +1,51 @@ + + + 4.0.0 + androidx.lifecycle + lifecycle-livedata + 2.0.0 + aar + Android Lifecycle LiveData + Android Lifecycle LiveData + https://developer.android.com/topic/libraries/architecture/index.html + 2017 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.arch.core + core-runtime + 2.0.0 + aar + compile + + + androidx.lifecycle + lifecycle-livedata-core + 2.0.0 + aar + compile + + + androidx.arch.core + core-common + 2.0.0 + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-runtime/2.0.0/lifecycle-runtime-2.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-runtime/2.0.0/lifecycle-runtime-2.0.0.aar new file mode 100644 index 00000000..0809d720 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-runtime/2.0.0/lifecycle-runtime-2.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-runtime/2.0.0/lifecycle-runtime-2.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-runtime/2.0.0/lifecycle-runtime-2.0.0.pom new file mode 100644 index 00000000..44328275 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-runtime/2.0.0/lifecycle-runtime-2.0.0.pom @@ -0,0 +1,49 @@ + + + 4.0.0 + androidx.lifecycle + lifecycle-runtime + 2.0.0 + aar + Android Lifecycle Runtime + Android Lifecycle Runtime + https://developer.android.com/topic/libraries/architecture/index.html + 2017 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.lifecycle + lifecycle-common + 2.0.0 + compile + + + androidx.arch.core + core-common + 2.0.0 + compile + + + androidx.annotation + annotation + 1.0.0 + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-viewmodel/2.0.0/lifecycle-viewmodel-2.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-viewmodel/2.0.0/lifecycle-viewmodel-2.0.0.aar new file mode 100644 index 00000000..b142a708 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-viewmodel/2.0.0/lifecycle-viewmodel-2.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-viewmodel/2.0.0/lifecycle-viewmodel-2.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-viewmodel/2.0.0/lifecycle-viewmodel-2.0.0.pom new file mode 100644 index 00000000..f859fb88 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/lifecycle/lifecycle-viewmodel/2.0.0/lifecycle-viewmodel-2.0.0.pom @@ -0,0 +1,37 @@ + + + 4.0.0 + androidx.lifecycle + lifecycle-viewmodel + 2.0.0 + aar + Android Lifecycle ViewModel + Android Lifecycle ViewModel + https://developer.android.com/topic/libraries/architecture/index.html + 2017 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/loader/loader/1.0.0/loader-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/loader/loader/1.0.0/loader-1.0.0.aar new file mode 100644 index 00000000..32c57746 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/loader/loader/1.0.0/loader-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/loader/loader/1.0.0/loader-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/loader/loader/1.0.0/loader-1.0.0.pom new file mode 100644 index 00000000..8bf9635b --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/loader/loader/1.0.0/loader-1.0.0.pom @@ -0,0 +1,58 @@ + + + 4.0.0 + androidx.loader + loader + 1.0.0 + aar + Android Support Library loader + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2011 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.core + core + 1.0.0 + aar + compile + + + androidx.lifecycle + lifecycle-livedata + 2.0.0 + aar + compile + + + androidx.lifecycle + lifecycle-viewmodel + 2.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/localbroadcastmanager/localbroadcastmanager/1.0.0/localbroadcastmanager-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/localbroadcastmanager/localbroadcastmanager/1.0.0/localbroadcastmanager-1.0.0.aar new file mode 100644 index 00000000..e9074ee4 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/localbroadcastmanager/localbroadcastmanager/1.0.0/localbroadcastmanager-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/localbroadcastmanager/localbroadcastmanager/1.0.0/localbroadcastmanager-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/localbroadcastmanager/localbroadcastmanager/1.0.0/localbroadcastmanager-1.0.0.pom new file mode 100644 index 00000000..abf4d6f4 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/localbroadcastmanager/localbroadcastmanager/1.0.0/localbroadcastmanager-1.0.0.pom @@ -0,0 +1,37 @@ + + + 4.0.0 + androidx.localbroadcastmanager + localbroadcastmanager + 1.0.0 + aar + Android Support Library Local Broadcast Manager + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2018 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/media/media/1.0.0/media-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/media/media/1.0.0/media-1.0.0.aar new file mode 100644 index 00000000..c07fcaeb Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/media/media/1.0.0/media-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/media/media/1.0.0/media-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/media/media/1.0.0/media-1.0.0.pom new file mode 100644 index 00000000..ae7652cc --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/media/media/1.0.0/media-1.0.0.pom @@ -0,0 +1,51 @@ + + + 4.0.0 + androidx.media + media + 1.0.0 + aar + Android Support Library media compat + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2011 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.core + core + 1.0.0 + aar + compile + + + androidx.versionedparcelable + versionedparcelable + 1.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/print/print/1.0.0/print-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/print/print/1.0.0/print-1.0.0.aar new file mode 100644 index 00000000..7bb51fd5 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/print/print/1.0.0/print-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/print/print/1.0.0/print-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/print/print/1.0.0/print-1.0.0.pom new file mode 100644 index 00000000..927140ae --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/print/print/1.0.0/print-1.0.0.pom @@ -0,0 +1,37 @@ + + + 4.0.0 + androidx.print + print + 1.0.0 + aar + Android Support Library Print + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2018 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/slidingpanelayout/slidingpanelayout/1.0.0/slidingpanelayout-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/slidingpanelayout/slidingpanelayout/1.0.0/slidingpanelayout-1.0.0.aar new file mode 100644 index 00000000..ebee0eee Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/slidingpanelayout/slidingpanelayout/1.0.0/slidingpanelayout-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/slidingpanelayout/slidingpanelayout/1.0.0/slidingpanelayout-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/slidingpanelayout/slidingpanelayout/1.0.0/slidingpanelayout-1.0.0.pom new file mode 100644 index 00000000..16e74a5b --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/slidingpanelayout/slidingpanelayout/1.0.0/slidingpanelayout-1.0.0.pom @@ -0,0 +1,51 @@ + + + 4.0.0 + androidx.slidingpanelayout + slidingpanelayout + 1.0.0 + aar + Android Support Library Sliding Pane Layout + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2018 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.core + core + 1.0.0 + aar + compile + + + androidx.customview + customview + 1.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/swiperefreshlayout/swiperefreshlayout/1.0.0/swiperefreshlayout-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/swiperefreshlayout/swiperefreshlayout/1.0.0/swiperefreshlayout-1.0.0.aar new file mode 100644 index 00000000..71d4748e Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/swiperefreshlayout/swiperefreshlayout/1.0.0/swiperefreshlayout-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/swiperefreshlayout/swiperefreshlayout/1.0.0/swiperefreshlayout-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/swiperefreshlayout/swiperefreshlayout/1.0.0/swiperefreshlayout-1.0.0.pom new file mode 100644 index 00000000..b889d0de --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/swiperefreshlayout/swiperefreshlayout/1.0.0/swiperefreshlayout-1.0.0.pom @@ -0,0 +1,51 @@ + + + 4.0.0 + androidx.swiperefreshlayout + swiperefreshlayout + 1.0.0 + aar + Android Support Library Custom View + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2018 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.core + core + 1.0.0 + aar + compile + + + androidx.interpolator + interpolator + 1.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/versionedparcelable/versionedparcelable/1.0.0/versionedparcelable-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/versionedparcelable/versionedparcelable/1.0.0/versionedparcelable-1.0.0.aar new file mode 100644 index 00000000..5cf661c3 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/versionedparcelable/versionedparcelable/1.0.0/versionedparcelable-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/versionedparcelable/versionedparcelable/1.0.0/versionedparcelable-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/versionedparcelable/versionedparcelable/1.0.0/versionedparcelable-1.0.0.pom new file mode 100644 index 00000000..3c56d50e --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/versionedparcelable/versionedparcelable/1.0.0/versionedparcelable-1.0.0.pom @@ -0,0 +1,43 @@ + + + 4.0.0 + androidx.versionedparcelable + versionedparcelable + 1.0.0 + aar + VersionedParcelable and friends + Provides a stable but relatively compact binary serialization format that can be passed across processes or persisted safely. + http://developer.android.com/tools/extras/support-library.html + 2018 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.collection + collection + 1.0.0 + compile + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/viewpager/viewpager/1.0.0/viewpager-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/viewpager/viewpager/1.0.0/viewpager-1.0.0.aar new file mode 100644 index 00000000..a7667298 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/viewpager/viewpager/1.0.0/viewpager-1.0.0.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/viewpager/viewpager/1.0.0/viewpager-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/viewpager/viewpager/1.0.0/viewpager-1.0.0.pom new file mode 100644 index 00000000..c0620950 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/androidx/viewpager/viewpager/1.0.0/viewpager-1.0.0.pom @@ -0,0 +1,51 @@ + + + 4.0.0 + androidx.viewpager + viewpager + 1.0.0 + aar + Android Support Library View Pager + The Support Library is a static library that you can add to your Android application in order to use APIs that are either not available for older platform versions or utility APIs that aren't a part of the framework APIs. Compatible on devices running API 14 or later. + http://developer.android.com/tools/extras/support-library.html + 2018 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/support + http://source.android.com + + + + androidx.annotation + annotation + 1.0.0 + compile + + + androidx.core + core + 1.0.0 + aar + compile + + + androidx.customview + customview + 1.0.0 + aar + compile + + + diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/animated-vector-drawable/24.0.0/animated-vector-drawable-24.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.pom diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/multidex/1.0.3/multidex-1.0.3.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/multidex/1.0.3/multidex-1.0.3.aar new file mode 100644 index 00000000..dae8a54c Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/multidex/1.0.3/multidex-1.0.3.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/multidex/1.0.3/multidex-1.0.3.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/multidex/1.0.3/multidex-1.0.3.pom new file mode 100644 index 00000000..6b9096a5 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/multidex/1.0.3/multidex-1.0.3.pom @@ -0,0 +1,29 @@ + + + 4.0.0 + com.android.support + multidex + 1.0.3 + aar + Android Multi-Dex Library + Library for legacy multi-dex support + + 2013 + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + The Android Open Source Project + + + + scm:git:https://android.googlesource.com/platform/frameworks/multidex + http://source.android.com + + diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.jar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.jar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.magic b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.magic similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.magic rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.magic diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.jar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.jar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.jar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.jar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.jar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.jar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.jar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/27.0.2-SNAPSHOT/support-annotations-27.0.2-SNAPSHOT.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-core-utils/26.1.0/support-core-utils-26.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-media-compat/26.1.0/support-media-compat-26.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-vector-drawable/24.0.0/support-vector-drawable-24.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement-license/12.0.0/play-services-basement-license-12.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/12.0.0/play-services-basement-12.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/15.0.0/play-services-basement-15.0.0.pom diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0-jetified/com.google.android.gms.play-services-basement-9.8.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0-jetified/com.google.android.gms.play-services-basement-9.8.0.aar new file mode 100644 index 00000000..4de4ea86 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0-jetified/com.google.android.gms.play-services-basement-9.8.0.aar differ diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-basement/9.8.0/play-services-basement-9.8.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks-license/12.0.0/play-services-tasks-license-12.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/12.0.0/play-services-tasks-12.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/android/gms/play-services-tasks/15.0.0/play-services-tasks-15.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.srcaar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.srcaar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.srcaar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/4.3.0/firebase-app-unity-4.3.0.srcaar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/mirror_from_gradle_cache.sh b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/mirror_from_gradle_cache.sh new file mode 100755 index 00000000..03ed1488 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/mirror_from_gradle_cache.sh @@ -0,0 +1,55 @@ +#!/bin/bash -eu +# Copyright (C) 2019 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +: ${GRADLE_USER_HOME:=${HOME}/.gradle} + +usage() { + echo "\ +Usage: $(basename $0) output_maven_dir module1..moduleN + +Copies the specified list of modules from the gradle cache to the +target directory. + +For example: +$(basename $0) . androidx.annotation:annotation:1.0.0 + +would copy the module 'androidx.annotation:annotation:1.0.0' and POM to +the directory './androidx/annotation/annotation/1.0.0' +" + exit 1 +} + +main() { + [ $# -eq 0 ] && usage + local cache=${GRADLE_USER_HOME}/caches/modules-2/files-2.1 + local output_dir="${1}" + shift 1 + local modules="$@" + for module in ${modules}; do + local -a components=(${module//:/ }) + local group="${components[0]}" + local group_dir="${group//./\/}" + local artifact="${components[1]}" + local version="${components[2]}" + local source_dir="${cache}/${group}/${artifact}/${version}" + local target_dir="${output_dir}/${group//.//}/${artifact}/${version}" + # Gradle stores a hash of each file in the directory structure so use + # a wildcard to ignore it when copying. + mkdir -p "${target_dir}" + find "${source_dir}" -type f | xargs -I@ cp "@" "${target_dir}" + done +} + +main "$@" diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1-bar.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1-bar.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1-bar.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1-foo.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1-foo.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1.aar differ diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1.pom new file mode 100644 index 00000000..9e3d37d0 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/1.0.1/classifier-1.0.1.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr + classifier + 1.0.1 + aar + + + + + diff --git a/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/maven-metadata.xml new file mode 100644 index 00000000..ba1496c5 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/classifier/maven-metadata.xml @@ -0,0 +1,13 @@ + + org.test.psr + classifier + + 1.0.1 + + 1.0.1 + + + + + + diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.aar b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.aar similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.aar rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.aar diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.pom b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.pom similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.pom rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.pom diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/maven-metadata.xml b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/maven-metadata.xml rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/maven-metadata.xml diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/readme.txt b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/readme.txt similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/readme.txt rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/readme.txt diff --git a/source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/update_from_readme.sh b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/update_from_readme.sh similarity index 100% rename from source/PlayServicesResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/update_from_readme.sh rename to source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/update_from_readme.sh diff --git a/source/AndroidResolver/scripts/file_to_maven_package.gradle b/source/AndroidResolver/scripts/file_to_maven_package.gradle new file mode 100644 index 00000000..1629a70b --- /dev/null +++ b/source/AndroidResolver/scripts/file_to_maven_package.gradle @@ -0,0 +1,1644 @@ +/* + * Copyright 2019 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +String helpText = """ +This script searches for libraries in Maven Central and Google Maven indexes, +if they are found the mapping of the local file to remote package is displayed. +If the local file isn't found a local Maven repository is created for the file +in TARGET_DIR so that it can be referenced as a Maven package. + +./gradlew -b """ + project.buildscript.sourceFile + """ \\ + \"-PTARGET_DIR=[target directory]\" \\ + \"-PFILE_TO_MAVEN_PACKAGE_SPEC=[fn0@spec0;fnN@specN]\" \\ + install + +TARGET_DIR (required project property): + Directory to create maven repository. + e.g -PTARGET_DIR=maven/repo/dir +FILE_TO_MAVEN_PACKAGE (required project property): + Semicolon separated filename to package spec map. + For example... + FILE_TO_MAVEN_PACKAGE=my/package.jar@org.foo:bar:1.2.3 + would create the maven package \"org.foo.bar:1.2.3\" in the TARGET_DIR with + the artifact \"my/package.jar\". + It's possible to omit the package spec e.g \"somelibrary.jar:;otherlib.aar:\" + and a placeholder package spec will be generated. + If the specified files are found in Maven Central or Google Maven's index + the remote resource will be used instead of the local file. + Since this property semicolon separated it needs to be quoted correctly + when specified via some command line shells. +MAVEN_REPOS (optional project property): + Optional property which adds to the list of Maven repositories to search. + Repositories must support the search-maven-org API + (see https://github.com/sonatype-nexus-community/search-maven-org/) + or support Google's Maven format. + This is a semicolon separated list of URIs e.g + \"-PMAVEN_REPOS=http://search.maven.org;\" + Since this property semicolon separated it needs to be quoted correctly + when specified via some command line shells. +MAVEN_INDEXES_DIR (optional project property): + Optional property that specifies the directory which contains the set of + maven indexes. This defaults to './maven-indexes'. +GOOGLE_MAVEN_INDEX_URI (optional project property): + Optional property which specifies a URI to a Google Maven Index JSON file + compressed with zip. + If this is not specified this defaults to + https://raw.githubusercontent.com/googlesamples/unity-jar-resolver/\ +master/google-maven-index.zip +""" + +buildscript { + repositories { + mavenLocal() + mavenCentral() + } + dependencies { + classpath "commons-io:commons-io:2.6" + } +} + + +import groovy.json.JsonBuilder +import groovy.json.JsonSlurper +import groovy.util.XmlSlurper +import groovy.util.slurpersupport.GPathResult +import java.nio.charset.Charset +import java.nio.file.Files +import java.nio.file.StandardCopyOption +import java.util.zip.ZipEntry +import java.util.zip.ZipException +import java.util.zip.ZipFile +import org.apache.commons.io.FilenameUtils +import org.apache.commons.io.input.BOMInputStream +import org.slf4j.LoggerFactory +import org.xml.sax.SAXException + +apply plugin: 'maven' + +// Indexes maven.google.com. +public class GoogleMavenIndexer { + + // Version of a Maven package. + public class Version { + // Package containing the version. + public Package containerPackage + // Version string. + public String version + // Backing store for the SHA-1 property. + private String sha1Store = "" + + /* + * Construct a package version. + * + * @param containerPackage Package is a version of. + * @param version Version string. + */ + public Version(Package containerPackage, String version) { + this.containerPackage = containerPackage + this.version = version + } + + /* + * Copy this version. + * + * @param ownerPackage Owner of the new version. + * + * @returns Copy of this version. + */ + public Version copy(Package ownerPackage) { + Version newVersion = new Version(ownerPackage, version) + newVersion.sha1 = sha1Cached + return newVersion + } + + /* + * Set the SHA-1 of the artifact. + * + * @param hash SHA-1 of the artifact. + */ + public void setSha1(String hash) { + sha1Store = hash + } + + /* + * Get the SHA-1 of the artifact. + * + * If the SHA-1 of the artifact isn't cached this will download the + * artifact. + * + * @returns SHA-1 of the artifact if successful, null otherwise. + */ + public String getSha1() { + if (sha1Store) return sha1Store + try { + File outputFile = getArtifact( + containerPackage.group.index.indexer.indexDir) + if (outputFile) { + sha1Store = outputFile.bytes.digest("SHA-1") + outputFile.delete() + } + } catch (FileNotFoundException error) { + // Store an invalid SHA-1 hash to prevent this artifact from being + // indexed again. + sha1Store = "missingresource" + } + return sha1Store + } + + /* + * Get the cached SHA-1 of this artifact. + * + * @returns Cached SHA-1 of this artifact. + */ + public String getSha1Cached() { + return sha1Store + } + + /* + * Get the package spec for this version. + * + * @return String package specifier (coordinate). + */ + public String toString() { + return [containerPackage.group.name, + containerPackage.name, + version].join(":") + } + + /* + * Get the POM URI + * + * @return Maven POM URI. + */ + public URI getPomUri() { + return containerPackage.group.getUri( + [containerPackage.name, + version, + sprintf("%s-%s.pom", containerPackage.name, version)].join("/")) + } + + /* + * Get an artifact URI for this version. + * + * @param packaging Packaging of the artifact. + * + * @return URI to the artifact. + */ + public URI getArtifactUri(String packaging) { + return containerPackage.group.getUri( + [containerPackage.name, + version, + sprintf("%s-%s.%s", containerPackage.name, version, + packaging)].join("/")) + } + + /* + * Download the artifact associated with this package. + * + * @param outputDir Directory to download the artifact to. + * + * @returns Artifact file if successful, null otherwise. + * + * @throws FileNotFoundException if the file referenced by the uri isn't + * found. + */ + public File getArtifact(File outputDir) throws FileNotFoundException { + logger.debug(sprintf("Downloading POM for %s from %s", toString(), + pomUri)) + Tuple2 xmlAndModifiedTime = getXml(pomUri) + if (!xmlAndModifiedTime) return null + def (GPathResult xml, Long modifiedTime) = xmlAndModifiedTime + String packaging = null + for (def node in xml.childNodes()) { + if (node.name() == "packaging") { + packaging = node.text() + break + } + } + if (!packaging) { + packaging = "jar" + logger.debug(sprintf("Packaging for artifact %s not found in POM %s, " + + "assuming %s", toString(), pomUri, packaging)) + } + URI artifactUri = getArtifactUri(packaging) + File outputFile = new File(outputDir, (toString().tokenize(":") + + [packaging]).join(".")) + logger.debug(sprintf("Downloading %s (%s) to %s", toString(), + artifactUri, outputFile)) + return containerPackage.group.index.indexer.getBinaryContents( + artifactUri, outputFile) + } + + /* + * Get the logger. + * + * @returns Logger for this object. + */ + public Logger getLogger() { + return containerPackage.logger + } + } + + // Maven package. + public class Package { + // Group containing the package. + public Group group + // Name of the package. + public String name + // List of versions in the package. + private List versionsStore = [] + + /* + * Construct a package. + * + * @param group Group the package is part of. + * @param name Name of this group. + */ + public Package(Group group, String name) { + this.group = group + this.name = name + } + + /* + * Copy this package. + * + * @param ownerGroup Owner of the new package. + * + * @returns Copy of this package. + */ + public Package copy(Group ownerGroup) { + Package newPackage = new Package(ownerGroup, name) + newPackage.versionsStore = versionsStore.collect { + return it.copy(newPackage) + } + return newPackage + } + + /* + * Add a version to the package. + * + * @param version Versions available for this package. + * + * @return List of newly added version instances. + */ + public List addVersions(List versions) { + List newVersions = versions.collect { new Version(this, it) } + versionsStore += newVersions + return newVersions + } + + /* + * Get the list of versions available for this package. + * + * @returns List of versions of this package. + */ + public List getVersions() { + return versionsStore + } + + /* + * Get the logger. + * + * @returns Logger for this object. + */ + public Logger getLogger() { + return group.logger + } + } + + // Group in the index. + public class Group { + // Index containing the group + public Index index + // Name of the group + public String name + // Time the group was last updated in milliseconds since the Unix epoch. + public long lastModifiedTime = -1 + // List of packages in the group. + private List packagesStore = [] + + /* + * Construct a group + * + * @param index Index that the group is part of. + * @param name Name of the group. + */ + public Group(Index index, String name) { + this.index = index + this.name = name + } + + /* + * Copy an existing group into this group. + * + * @param group Group to copy from. + */ + public void copyFrom(Group group) { + name = group.name + lastModifiedTime = group.lastModifiedTime + packagesStore = group.packagesStore.collect { + return it.copy(this) + } + } + + /* + * Get a URI to this group's index. + * + * @return Index URI. + */ + public URI getIndexUri() { + return getUri("group-index.xml") + } + + /* + * Add a package to this group. + * + * @param packageName Name of the package. + * @param versions List of versions available for this package. + */ + public Package addPackage(String packageName, List versions) { + Package pkg = new Package(this, packageName) + pkg.addVersions(versions) + packagesStore.add(pkg) + return pkg + } + + /* + * Get the set of cached packages for this group. + * + * @returns List of packages associated with this group. + */ + public List getPackagesCached() { + return packagesStore + } + + /* + * Get the packages associated with this group, downloading them if + * neccesary. + * + * @returns List of packages associated with this group if successful, + * null otherwise. + */ + public List getPackages() { + // If the group has already been downloaded just return the set of + // packages. + if (packagesStore) return packagesStore + logger.info( + sprintf("Downloading group index from %s", indexUri)) + Tuple2 xmlAndModifiedTime = getXml(indexUri) + if (xmlAndModifiedTime == null) return null + + def (GPathResult xml, Long modifiedTime) = xmlAndModifiedTime + lastModifiedTime = modifiedTime + packagesStore = [] + xml.childNodes().each { node -> + String packageName = node.name() + String commaSeparatedVersions = node.attributes["versions"] + if (commaSeparatedVersions) { + addPackage(packageName, commaSeparatedVersions.tokenize(",")) + } else { + logger.warn(sprintf("In %s no versions found for package %s", + indexUri, packageName)) + } + } + return packagesStore + } + + /* + * Get a URI relative to this group. + * + * @param path Path to append to this URI of this group. + * + * @return URI to the path relative to this group. + */ + public URI getUri(String path) { + return index.indexer.buildUri(name.tokenize(".").join("/"), path) + } + + /* + * Get the logger. + * + * @returns Logger for this object. + */ + public Logger getLogger() { + return index.logger + } + } + + // Master index of the repository. + public class Index { + // Indexer this was created from. + public GoogleMavenIndexer indexer + // Time the index was last updated in milliseconds since the Unix epoch. + public long lastModifiedTime + // List of groups in the index. + private List groupsStore = [] + // Artifact version by SHA-1, updated by performIndex(). + private Map> versionBySha1 = [:] + + /* + * Construct an index. + * + * @param indexer Indexer this index was read from. + * @param lastModifiedTime Last time the index was updated in milliseconds + * since the Unix epoch. + */ + public Index(GoogleMavenIndexer indexer, long lastModifiedTime) { + this.indexer = indexer + this.lastModifiedTime = lastModifiedTime + } + + /* + * Add a groups to the index. + * + * @param names List of group names. + * + * @return List of newly added groups. + */ + List addGroups(List names) { + List newGroups = names.collect { return new Group(this, it) } + groupsStore += newGroups + return newGroups + } + + /* + * Get the groups in this index. + * + * @return List of groups. + */ + List getGroups() { + return groupsStore + } + + /* + * Search this index for an artifact by SHA-1 + * + * @param hash SHA-1 to search for. + * + * @returns List of matching versions. + */ + List findVersionsBySha1(String hash) { + List matches = versionBySha1[hash] + return matches ?: [] + } + + /* + * Update the SHA-1 to artifact map. + */ + void updateSha1ArtifactMap() { + versionBySha1 = [:] + groups.each { Group group -> + group.packagesCached.each { Package pkg -> + pkg.versions.each { Version version -> + String sha1 = version.sha1Cached + if (sha1) { + List matches = versionBySha1[sha1] + if (matches) { + matches.add(version) + } else { + matches = [version] + } + versionBySha1[sha1] = matches + } + } + } + } + } + + /* + * Index all artifacts. + * + * This method writes a checkpoint of the index to a file before downloading + * each group index or indexing an artifact. + * + * @param indexFile File to write to while creating the index. + * + * @returns true if successful, false otherwise. + */ + public boolean performIndex(File indexFile) { + // Calculate the total number of artifacts with and without SHA1s + // so that it's possible to display progress when downloading artifacts to + // calculate hashes. + int totalArtifacts = 0 + int cachedSha1s = 0 + for (Group group in groups) { + // If this is going to download the group index, checkpoint. + if (!group.packagesCached && !writeIndex(indexFile)) return false + List packages = group.packages + if (packages == null) { + logger.error( + sprintf("Failed to download packages for group %s (%s)", + group.name, indexer.repo)) + return false + } + group.packages.each { Package pkg -> + pkg.versions.each { Version version -> + if (version.sha1Cached) { + cachedSha1s++ + } + totalArtifacts++ + } + } + } + + int totalProgress = 0 + int sha1CacheProgress = 0 + int sha1sToCache = totalArtifacts - cachedSha1s + for (Group group in groups) { + for (Package pkg in group.packages) { + for (Version version in pkg.versions) { + totalProgress++ + if (!version.sha1Cached) { + if (!writeIndex(indexFile)) return false; + sha1CacheProgress++ + logger.info( + sprintf( + "Indexing % 5.1f%% (%d/%d) complete " + + "(% 5.1f%% %d/%d indexed)", + ((float)totalProgress / (float)totalArtifacts) * 100.0, + totalProgress, totalArtifacts, + ((float)sha1CacheProgress / (float)sha1sToCache) * 100.0, + sha1CacheProgress, sha1sToCache)) + } + if (!version.sha1) { + logger.error(sprintf("Failed to calculate SHA-1 of artifact %s " + + "(%s).", version, indexer.repo)) + return false + } + } + } + } + updateSha1ArtifactMap() + if (sha1sToCache) { + if (!writeIndex(indexFile)) return false + logger.info(sprintf("Indexing of %s complete", indexer.repo)) + } + return true + } + + /* + * Convert the index to a data structure that can be stored in JSON. + * + * The last modified time set to -1 for all groups that are not completely + * indexed. This forces the group to be fetched from a remote index when + * initialized. + * + * @returns object which can be written to JSON using the JsonBuilder class. + */ + public def toJsonData() { + // Convert to JSON + def jsonData = ["update_time": lastModifiedTime, "groups": []] + groups.each { Group group -> + def packagesJson = [] + boolean allVersionsIndexed = true + group.packagesCached.each { Package pkg -> + def versionsJson = [] + pkg.versions.each { Version version -> + if (!version.sha1Cached) { + allVersionsIndexed = false + } + versionsJson += ["version": version.version, + "sha1": version.sha1Cached] + } + packagesJson += ["package": pkg.name, + "versions": versionsJson] + } + jsonData["groups"] += ["group": group.name, + "update_time": (allVersionsIndexed ? + group.lastModifiedTime : -1), + "packages": packagesJson] + } + return jsonData + } + + /* + * Write the specified index to a JSON file. + * + * @param indexFile File to write to. + * + * @returns true if the index is written successfully, false otherwise. + */ + public boolean writeIndex(File indexFile) { + logger.debug(sprintf("Write index of %s to file %s", indexer.repo, + indexFile)) + // Write the index file. + try { + indexFile.parentFile.mkdirs() + indexFile.write((new JsonBuilder(toJsonData())).toString()) + } catch (IOException error) { + logger.error(sprintf("Failed to write index for %s to file %s (%s)", + indexer.repo, indexFile, error)) + return false + } + return true + } + + /* + * Clear the index. + */ + public void clear() { + lastModifiedTime = -1 + groupsStore = [] + versionBySha1 = [:] + } + + /* + * Read the index from a JSON string into this instance. + * + * @param indexJson JSON string containing the index. + * @param indexFilename Name of the file the JSON string was read from. + * + * @returns Index if successful, null otherwise. + */ + public Index fromJsonString(String indexJson, String indexFilename) { + clear() + def jsonData + try { + jsonData = (new JsonSlurper()).parseText(indexJson) + } catch (Exception error) { + logger.error(sprintf("Failed to parse index from %s for repo %s (%s)", + indexFilename, indexer.repo, error)) + return null + } + def lastModifiedTimeJson = jsonData["update_time"] + if (!(lastModifiedTimeJson instanceof Number)) { + logger.error(sprintf("'update_time' (%s) not an integer in %s", + lastModifiedTimeJson.class, indexFilename)) + return null + } + lastModifiedTime = lastModifiedTimeJson + def groupsJson = jsonData["groups"] + if (!(groupsJson instanceof ArrayList)) { + logger.error(sprintf("'groups' (%s) map invalid in %s", + groupsJson.class, indexFilename)) + clear() + return null + } + for (def groupJson in groupsJson) { + def groupName = groupJson["group"] + def groupLastModifiedTime = groupJson["update_time"] + def packagesJson = groupJson["packages"] + if (!(groupName instanceof String && + groupLastModifiedTime instanceof Number && + packagesJson instanceof ArrayList)) { + logger.error( + sprintf("'group' (%s), 'update_time' (%s) or 'packages' (%s) " + + "invalid in %s", groupName.class, groupLastModifiedTime.class, + packagesJson.class, indexFilename)) + clear() + return null + } + Group group = addGroups([groupName])[0] + group.lastModifiedTime = groupLastModifiedTime + for (def packageJson in packagesJson) { + def packageName = packageJson["package"] + def versionsJson = packageJson["versions"] + if (!(packageName instanceof String && + versionsJson instanceof ArrayList)) { + logger.error( + sprintf("'package' (%s) or 'versions' (%s) invalid in %s", + packageName.class, versionsJson.class, indexFilename)) + clear() + return null + } + Package pkg = group.addPackage(packageName, []) + for (def versionJson in versionsJson) { + def versionId = versionJson["version"] + def sha1 = versionJson["sha1"] + if (!(versionId instanceof String && + sha1 instanceof String)) { + logger.error(sprintf("'version' (%s) or 'sha1' (%s) invalid in %s", + versionId.class, sha1.class, indexFilename)) + clear() + return null + } + pkg.addVersions([versionId])[0].sha1 = sha1 + } + } + } + updateSha1ArtifactMap() + return this + } + + + /* + * Get the logger. + * + * @returns Logger for this object. + */ + public Logger getLogger() { + return indexer.logger + } + } + + // Canonical URI to the Google Maven repo. + public static URI googleMaven = + new URI("https://dl.google.com/dl/android/maven2") + // HTTP connection and read timeout + public static int httpTimeoutMilliseconds = 20 * 1000 + // URI for the cached Maven index zip file. + public static URI googleMavenIndexUri = + new URI("https://raw.githubusercontent.com/googlesamples/" + + "unity-jar-resolver/master/google-maven-index.zip") + + // Repo URI. + public URI repo + // Directory to store the index and temporary files. + public File indexDir + // Index JSON file within the index directory. + public File indexFile + // Logs indexer operations. + public Logger logger = LoggerFactory.getLogger(this.class.name) + // Index cached for this object. + private Index indexStore = null + // Index read from local storage. + private Index localIndex = null + + /* + * Indexes a Google Maven repo. + * + * @param repo Repository to index. + * @param indexDir Directory to load / save index and store temporary files. + * @param logger If non-null, overrides this classes's logger. + */ + public GoogleMavenIndexer(URI repo, File indexDir, Logger logger) { + this.repo = repo + this.indexDir = indexDir + if (logger != null) this.logger = logger + indexFile = new File(indexDir, "index.json") + } + + /* + * Get the master index URI. + * + * @return URI to the master index XML file. + */ + public URI getMasterIndexUri() { + return new URI(repo.toString() + "/master-index.xml") + } + + /* + * Get the master index. + * + * @return Tuple of index contents and last modified time in milliseconds + * since the Unix epoch if successful, null otherwise. + */ + public Index getMasterIndex() { + if (indexStore) return indexStore + localIndex = readIndex() + + // If we have a cached index, and it isn't out of date return it. + if (localIndex) { + long remoteLastModifiedTime = getLastModifiedTime(masterIndexUri) + logger.debug(sprintf("Remote index modified %d vs. local %d", + remoteLastModifiedTime, + localIndex.lastModifiedTime)) + if (remoteLastModifiedTime <= localIndex.lastModifiedTime) { + indexStore = localIndex + return indexStore + } + } + + logger.info(sprintf("Downloading root index %s", masterIndexUri)) + Tuple2 xmlAndModifiedTime = getXml(masterIndexUri) + if (xmlAndModifiedTime == null) return null + def (GPathResult xml, Long lastModifiedTime) = xmlAndModifiedTime + indexStore = new Index(this, lastModifiedTime) + indexStore.addGroups(xml.childNodes().collect { it.name }) + + // If we have a cached index, try merging the existing data. + if (localIndex) { + Map groupByName = indexStore.groups.collectEntries { + return [it.name, it] + } + localIndex.groups.each { Group localGroup -> + Group newGroup = groupByName[localGroup.name] + lastModifiedTime = getLastModifiedTime(localGroup.indexUri) + if (lastModifiedTime <= localGroup.lastModifiedTime) { + newGroup.copyFrom(localGroup) + } + } + } + return indexStore + } + + /* + * Download and parse XML from handling parse exceptions. + * + * @param uri URI to download XML from. + * + * @returns Tuple of the result and last modified time if successful, + * null otherwise. + */ + public Tuple2 getXml(URI uri) { + Tuple2 contentsAndModifiedTime = getContents(uri) + if (contentsAndModifiedTime == null) return null + def (String contents, Long lastModifiedTime) = contentsAndModifiedTime + try { + return new Tuple2( + (new XmlSlurper()).parseText(contents), lastModifiedTime) + } catch (SAXException error) { + logger.error(sprintf("Failed to parse XML downloaded from %s (%s)", + uri, error)) + return null + } + } + + /* + * Read the index from the local or remote cache. + * + * @returns Index if successful, null otherwise. + */ + public Index readIndex() { + Index newIndex = new Index(this, -1) + if (!indexFile.exists()) { + // Try downloading the index. + File indexArchive = new File(indexFile.path + ".zip") + try { + logger.info(sprintf("Downloading index snapshot %s --> %s", + googleMavenIndexUri, indexArchive)) + indexArchive = getBinaryContents(googleMavenIndexUri, indexArchive) + } catch (FileNotFoundException error) { + indexArchive = null + } + if (indexArchive) { + try { + ZipFile zipFile = new ZipFile(indexArchive) + for (ZipEntry zipEntry in zipFile.entries()) { + if ((new File(zipEntry.name)).name == indexFile.name) { + copyStream(zipFile.getInputStream(zipEntry), + indexFile.newOutputStream()) + break + } + } + } catch (IOException error) { + logger.error(sprintf("Failed while reading index %s into %s (%s)", + indexArchive, indexFile, error)) + if (indexFile.exists()) indexFile.delete() + } catch (ZipException error) { + logger.error(sprintf("Detected corrupt index zip file %s while " + + "unpacking to %s (%s)", indexArchive, indexFile, + error)) + if (indexFile.exists()) indexFile.delete() + } finally { + indexArchive.delete() + } + } + } + if (indexFile.exists()) { + try { + return newIndex.fromJsonString(indexFile.text, indexFile.path) + } catch (IOException error) { + logger.error(sprintf("Unable to read index file %s for repo %s (%s)", + indexFile, repo, error)) + return null + } + } + } + + /* + * Update the specified index file. + * + * @return Index instance if successful, null otherwise + */ + Index updateIndex() { + Index index = masterIndex + return index && index.performIndex(indexFile) ? index : null + } + + /* + * Builds a URI in the form host/group/path. + * + * @param group Period separated group that is converted to a / separated + * path in the URI. + * @param path Path to append to the URI.. + */ + public URI buildUri(String group, String path) { + List components = [repo.toString()] + if (group) components += group.tokenize(".") + if (path) components += [path] + return new URI(components.join("/")) + } + + /* + * Open a HTTP connection. + * + * @param uri URI to connect to. + * + * @return Connection instance. + */ + public HttpURLConnection openConnection(URI uri) { + HttpURLConnection connection = uri.toURL().openConnection() + connection.connectTimeout = httpTimeoutMilliseconds + connection.readTimeout = httpTimeoutMilliseconds + return connection + } + + /* + * Read a character stream into a string. + * + * @param inputStream Stream to read. + * + * @return String read from the stream. + */ + public String readInputStreamToString(InputStream inputStream) { + StringBuilder content = new StringBuilder() + BOMInputStream bomInputStream = new BOMInputStream(inputStream) + Charset charset = Charset.defaultCharset() + if (bomInputStream.hasBOM()) { + charset = Charset.availableCharsets()[ + bomInputStream.getBOMCharsetName()] + } + BufferedReader reader = new BufferedReader( + new InputStreamReader(bomInputStream)) + String line + while ((line = reader.readLine()) != null) { + content.append(line + "\n") + } + return content.toString() + } + + /* + * Read the text contents of a URI and retrieve the last modified time. + * + * @param url URI to query. + * + * @return Tuple of URI contents and last modified time in milliseconds + * since the Unix epoch if successful, null otherwise. + */ + public Tuple2 getContents(URI uri) { + if (uri.scheme == "file") { + File inputFile = new File(uri) + try { + return new Tuple2( + readInputStreamToString(inputFile.inputStream), + inputFile.lastModified) + } catch (IOException error) { + logger.error(sprintf("Failed to read %s (%s)", uri, error)) + return null + } + } + HttpURLConnection connection = null + long lastModifiedTime + String content + try { + connection = openConnection(uri) + connection.requestMethod = "GET" + content = readInputStreamToString(connection.inputStream) + lastModifiedTime = connection.lastModified + } catch (IOException error) { + logger.error(sprintf("Failed to fetch %s (%s)", uri, error)) + return null + } finally { + if (connection != null) connection.disconnect() + } + return new Tuple2(content, lastModifiedTime) + } + + /* + * Copy from an input stream to an output stream. + * + * @param inputStream Stream to copy from. + * @param outputStream Stream to write to. + */ + public static void copyStream(InputStream inputStream, + OutputStream outputStream) { + byte[] buffer = new byte[4096] + int readSize + try { + while ((readSize = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, readSize) + } + } finally { + outputStream.close() + inputStream.close() + } + } + + /* + * Read binary contents of a URI and save to a file. + * + * @param uri URI to query. + * @param outputFile File to write to. + * + * @return Reference to the supplied outputFile if successful, null otherwise. + * + * @throws FileNotFoundException if the file referenced by the uri isn't + * found. + */ + public File getBinaryContents(URI uri, File outputFile) + throws FileNotFoundException { + if (uri.scheme == "file") { + File inputFile = new File(uri) + try { + outputFile.parentFile.mkdirs() + copyStream(inputFile.newInputStream(), outputFile.newOutputStream()) + } catch (IOException error) { + if (outputFile.exists()) outputFile.delete() + logger.warn(sprintf("Failed to copy %s to %s (%s)", + uri, outputFile, error)) + if (error instanceof FileNotFoundException) throw error + return null + } + return outputFile + } + HttpURLConnection connection = null + OutputStream outputStream = null + InputStream inputStream = null + try { + connection = openConnection(uri) + connection.requestMethod = "GET" + outputFile.parentFile.mkdirs() + copyStream(connection.inputStream, outputFile.newOutputStream()) + } catch (IOException error) { + if (outputFile.exists()) outputFile.delete() + logger.warn(sprintf("Failed to download %s to %s (%s)", + uri, outputFile, error)) + if (error instanceof FileNotFoundException) throw error + return null + } finally { + if (inputStream != null) inputStream.close() + if (outputStream != null) outputStream.close() + if (connection != null) connection.disconnect() + } + return outputFile + } + + /* + * Get the last modified time of the specified URI + * + * @param uri URI to query. + * + * @return Last modified time in milliseconds since the Unix epoch if + * successful, -1 otherwise. + */ + public long getLastModifiedTime(URI uri) { + if (uri.scheme == "file") { + File inputFile = new File(uri) + try { + return inputFile.lastModified() + } catch (IOException error) { + logger.warn(sprintf("Failed to get last modified time of %s (%s)", + uri, error)) + return -1 + } + } + HttpURLConnection connection + long lastModifiedTime = -1 + try { + connection = openConnection(uri) + connection.setRequestMethod("HEAD") + if (connection.responseCode == HttpURLConnection.HTTP_OK) { + lastModifiedTime = connection.lastModified + } else { + logger.warn(sprintf("Failed to get last modified time of %s " + + "(HTTP status=%s)", uri, connection.responseCode)) + } + connection.disconnect() + } catch (IOException error) { + logger.warn(sprintf("Failed to get last modified time of %s (%s)", + uri, error)) + } + return lastModifiedTime + } + + /* + * Generate a local file path from a directory and URI. + * + * @param containerDir Directory that is the root of the path. + * @param uri URI to convert to a path to store under the directory. + */ + public static File uriToPath(File containerDir, URI uri) { + return new File(containerDir, + uri.host + File.separator + + uri.path.replaceAll('/', File.separator)) + } +} + +// Maven package specifier. +public class MavenPackage { + // Google Maven Indexer, this should typically not be accessed directly. + // Instead use the thread safe accessor methods in this class. + public static GoogleMavenIndexer googleMavenIndexerInstance = null + // Whether the index is fresh. + private static boolean googleMavenIndexerUpdated = false + // The Google Maven Indexer is not thread safe so this ensures we only + // access it from one thread. + private static Object googleMavenIndexerLock = new Object() + + public Logger logger = LoggerFactory.getLogger(this.class.name) + + // Source file for a maven package. + public File sourceFile = null + // Group component of a maven package. + public String group = "" + // Artifact component of a maven package. + public String artifact = "" + // Version a maven package. + public String version = "" + // Type of the artifact. + public String artifactType = "" + // Maven repo where this is found. + public URI repo = null + + /* + * Create a package specifier from a string. + * + * @param packageSpecString String in the form group:artifact:version. + * If this is an empty string or null, the package spec is generated from + * the sourceFile name. + * @param repo Repo where this package is hosted or null if it's unknown. + * @param sourceFile Filename to parse the artifact type (extension) + * from. + * @param logger If non-null, overrides this classes's logger. + * + * @throws InvalidUserDataException if package spec format is invalid or + * sourceFile does not exist. + */ + public MavenPackage(String packageSpecString, URI repo, File sourceFile, + Logger logger) throws InvalidUserDataException { + if (!packageSpecString) { + packageSpecString = generatePackageName(sourceFile, "org.psr", "0.0.0") + } + List components = packageSpecString.tokenize(":") + if (components.size() != 3) { + throw new InvalidUserDataException(sprintf("Invalid package spec %s", + packageSpecString)) + } + if (!sourceFile.exists()) { + throw new InvalidUserDataException(sprintf("%s does not exist", + sourceFile)) + } + this.repo = repo + this.sourceFile = sourceFile + if (logger != null) this.logger = logger + group = components[0] + artifact = components[1] + version = components[2] + artifactType = sourceFile.name.substring( + sourceFile.name.lastIndexOf('.') + 1) + } + + /* + * Generate a Maven package spec from a filename. + * + * @param sourceFile File to generate the Maven package name from. + * @param group Group to use in the generated spec. + * @param version Version to use in the generated spec. + * + * @return Maven package spec string. + */ + private static String generatePackageName(File sourceFile, String group, + String version) { + // Generate a package name from the file path. + List sourceFilePathComponents = + FilenameUtils.getPath( + FilenameUtils.separatorsToUnix(sourceFile.path)) + .tokenize("/").collect { it.toLowerCase() } + String packageName = sourceFilePathComponents.join(".") + + // Try trimming the path starting at common directory names used to host + // libraries. + for (String component in ["assets", "m2repository"]) { + int offset = sourceFilePathComponents.indexOf(component) + if (offset >= 0) { + packageName = sourceFilePathComponents.subList( + offset + 1, sourceFilePathComponents.size()).join(".") + break + } + } + return [group, packageName, version].join(':') + } + + /* + * Search the source artifact in a set of maven repos. + * + * @param repos List of repo URIs to search. + * + * @return MavenPackage instance if it's found, null otherwise. + */ + public MavenPackage search(List repos) { + String sha1 = sourceFileSha1 + for (URI repo in repos) { + for (List foundPackages in [ + searchGoogleMaven(sha1), + searchUsingMavenSolr(repo, sha1) + ]) { + if (foundPackages) { + if (foundPackages.size() > 1) { + logger.warn( + sprintf("Found multiple matches for %s in repo %s [%s], ignoring", + sourceFile, repo, + foundPackages.collect { it.toString() }.join(", "))) + continue + } + MavenPackage foundPackage = foundPackages[0] + logger.debug(sprintf("Found %s in repo %s (%s)", sourceFile, repo, + foundPackage)) + return foundPackage + } + } + logger.debug(sprintf("%s not found in repo %s", sourceFile, repo)) + } + return null + } + + /* + * Attempts to search a Maven repo that is indexed by Apache Solr. + * + * @param repo Repo URI to search. + * @param sha1 SHA1 of the artifact to find. + * + * @return List of MavenPackage instances that are found. + */ + private List searchUsingMavenSolr(URI repo, String sha1) { + List foundPackages = [] + URI searchRepo = repo + // Search using the Maven REST API https://search.maven.org/classic/#api + // To search Maven central we need to hit search.maven.org. + if (repo.host == "maven.org" || repo.host == "repo.maven.apache.org") { + searchRepo = new URI("https://search.maven.org/") + } + try { + URL searchUrl = + (new URI(searchRepo.scheme, searchRepo.host, "/solrsearch/select", + sprintf("q=1:\"%s\"&rows=20&wt=json", sha1), "")).toURL() + logger.debug(sprintf("Searching %s for %s (%s)", + searchUrl, sourceFile, sha1)) + def json = (new JsonSlurper()).parseText(searchUrl.text) + def docs = json.response["docs"] as List + if (docs) { + for (def doc in docs) { + String foundGroup = doc["g"] as String + String foundArtifact = doc["a"] as String + String foundVersion = doc["v"] as String + String foundPackaging = doc["p"] as String + if (foundGroup && foundArtifact && foundVersion && foundPackaging) { + foundPackages.add( + new MavenPackage( + sprintf("%s:%s:%s", foundGroup, foundArtifact, foundVersion), + repo, sourceFile, logger)) + } + } + } + } catch (Exception e) { + logger.warn(sprintf("Search of %s failed (%s)", repo.toString(), + e.toString())) + } + return foundPackages + } + + /* + * Assign the Google Maven indexer. + * + * @param googleMavenIndexer Index to search for artifacts. + */ + public static void setGoogleMavenIndexer( + GoogleMavenIndexer googleMavenIndexer) { + synchronized (MavenPackage.googleMavenIndexerLock) { + MavenPackage.googleMavenIndexerInstance = googleMavenIndexer + } + } + + /* + * Update the Google Maven index. + * + * @returns Reference to the index file if successful, null otherwise. + */ + public static File updateGoogleMavenIndex() { + synchronized (MavenPackage.googleMavenIndexerLock) { + GoogleMavenIndexer indexer = MavenPackage.googleMavenIndexerInstance + if (indexer != null) { + if (MavenPackage.googleMavenIndexerUpdated || + indexer.updateIndex() != null) { + MavenPackage.googleMavenIndexerUpdated = true + return indexer.indexFile + } + } + } + return null + } + + /* + * Attempts to index and subsequently search a Maven repo. + * + * @param sha1 SHA1 of the artifact to find. + * + * @return List of MavenPackage instances that are found. + */ + private List searchGoogleMaven(String sha1) { + synchronized (MavenPackage.googleMavenIndexerLock) { + updateGoogleMavenIndex() + GoogleMavenIndexer indexer = MavenPackage.googleMavenIndexerInstance + if (indexer != null) { + GoogleMavenIndexer.Index index = indexer.masterIndex + if (index) { + List matches = + index.findVersionsBySha1(sha1) + if (matches) { + return matches.collect { + new MavenPackage(it.toString(), indexer.repo, sourceFile, logger) + } + } + } + } + } + return [] + } + + /* + * Convert to a package spec string. + * + * @return Package spec string. + */ + public String toString() { + return sprintf("%s:%s:%s", group, artifact, version) + } + + /* + * Convert to a string that can be used in a task name. + * + * @return Name friendly for tasks. + */ + public String getTaskName() { + return sprintf("%s.%s", group, artifact) + } + + /* + * Generate a path to a Maven POM directory given package spec components. + * + * @return Path to the Maven POM directory. + */ + public File getPomDirectory() { + return new File(sprintf("%s/%s/%s", group, artifact, version)) + } + + /* + * Generate a path to a Maven POM file given package spec components. + * + * @return Path to the Maven POM. + */ + public File getPomFile() { + return new File(sprintf("%s/%s-%s.pom", pomDirectory, artifact, version)) + } + + /* + * Generate a path to the Maven artifact file. + * + * @return Path to the Maven artifact. + */ + public File getArtifactFile() { + return new File(sprintf("%s/%s-%s.%s", pomDirectory, artifact, version, + artifactType)) + } + + /* + * Get the SHA1 of the source file. + * + * @return SHA1 string of the source file. + */ + public String getSourceFileSha1() { + return sourceFile.bytes.digest("SHA-1") + } +} + +project.ext { + // Parse the target directory. + project.ext.targetDir = null + if (project.hasProperty("TARGET_DIR")) { + project.ext.targetDir = new File(project.getProperty("TARGET_DIR")) + } + + List mavenSearchUris = [project.project.repositories.mavenCentral().url] + // Retrieve a list of command line specified maven repo URIs. + if (project.hasProperty("MAVEN_REPOS")) { + mavenSearchUris = [] + project.getProperty("MAVEN_REPOS").tokenize(";").each { + mavenSearchUris.push(new URI(it)) + } + } + project.ext.mavenSearchUris = mavenSearchUris + + // Override the default indexes directory. + project.ext.indexesDir = new File("maven-indexes") + if (project.hasProperty("MAVEN_INDEXES_DIR")) { + project.ext.indexesDir = new File(project.getProperty("MAVEN_INDEXES_DIR")) + } + + // Override the default Google maven index zip file. + if (project.hasProperty("GOOGLE_MAVEN_INDEX_URI")) { + GoogleMavenIndexer.googleMavenIndexUri = + new URI(project.getProperty("GOOGLE_MAVEN_INDEX_URI")) + } + + // Propagate Gradle's default connection timeout to the indexer. + if (project.hasProperty("http.connectionTimeout")) { + GoogleMavenIndexer.httpTimeoutMilliseconds = + Integer.parseInt(project.getProperty("http.connectionTimeout")) + } + + // Propagate Gradle's Google Maven URI to the indexer. + GoogleMavenIndexer.googleMaven = project.project.repositories.google().url + + // Create the Google Maven Indexer and assign ownership to MavenPackage. + MavenPackage.setGoogleMavenIndexer( + new GoogleMavenIndexer( + GoogleMavenIndexer.googleMaven, + GoogleMavenIndexer.uriToPath( + project.ext.indexesDir, GoogleMavenIndexer.googleMaven), logger)) + + // Parse the file to maven package spec map. + project.ext.mavenPackages = [] + if (project.hasProperty("FILE_TO_MAVEN_PACKAGE")) { + String fileToMavenPackageSpec = + project.getProperty("FILE_TO_MAVEN_PACKAGE") + project.ext.mavenPackages = + fileToMavenPackageSpec.tokenize(";").findResults { + List tokens = it.tokenize("@") + if (tokens.size() > 2) { + logger.error( + sprintf("Ignoring invalid file and package spec %s", it)) + return null + } + File sourceFile = new File(tokens[0]) + String packageSpec = tokens.size() > 1 && tokens[1] ? tokens[1] : null + try { + return new MavenPackage(packageSpec, null, sourceFile, logger) + } catch (InvalidUserDataException e) { + logger.error(sprintf("Ignoring %s (%s)", e.message, it)) + return null + } + } + } + + // Display parsed arguments. + if (project.ext.targetDir) { + logger.info(sprintf("TARGET_DIR: %s", project.ext.targetDir)) + } + project.ext.mavenPackages.each { + logger.info(sprintf("FILE_TO_MAVEN_PACKAGE: file=%s package=%s", + it.sourceFile, it.toString())) + } + project.ext.mavenSearchUris.each { + logger.info(sprintf("MAVEN_REPOS: url=%s", it)) + } + logger.info(sprintf("MAVEN_INDEXES_DIR: %s", project.ext.indexesDir)) + logger.info(sprintf("GOOGLE_MAVEN_INDEX_URI: %s", + GoogleMavenIndexer.googleMavenIndexUri)) +} + +// Register a task to generate a Maven package for each source artifact. +// These tasks will only generate a Maven package for the source artifact +// if the artifact isn't found in a remote Maven repository. +List createMavenPackageTasks = + project.ext.mavenPackages.collect { MavenPackage mavenPackage -> + String taskName = "createPomFor" + mavenPackage.taskName + + // Register the task with a provider so that it isn't configured unless + // it's going to be executed. + TaskProvider createPomTaskProvider = tasks.register(taskName) + createPomTaskProvider.configure( + { + // Search for the package in a remote repository. + MavenPackage foundPackage = + mavenPackage.search(project.ext.mavenSearchUris) + // If the package is in a remote repository, just report the remote + // location. + if (foundPackage) { + description sprintf("Print the location of the Maven artifact %s (%s)", + foundPackage.sourceFile, foundPackage) + + ext.mavenPackage = foundPackage + doLast { + println sprintf("Not creating POM for %s, found at %s (%s)", + foundPackage.sourceFile, foundPackage, + foundPackage.repo) + } + } else { + description sprintf("Create a Maven POM for %s at %s in " + + "TARGET_DIR (%s)", + mavenPackage.sourceFile, mavenPackage, + project.ext.targetDir ?: "not set") + + if (!project.ext.targetDir) { + logger.error( + sprintf("TARGET_DIR must be specified to create task %s. " + + "This task will do nothing!", taskName)) + return + } + + File pomFile = new File(project.ext.targetDir, + mavenPackage.pomFile.path) + File artifactFile = new File(project.ext.targetDir, + mavenPackage.artifactFile.path) + + // Create a MavenPackage that points at the generated repository. + ext.mavenPackage = new MavenPackage( + mavenPackage.toString(), project.ext.targetDir.toURI(), + mavenPackage.sourceFile, logger) + + // Generate a local Maven POM for the package. + inputs.files files([mavenPackage.sourceFile]) + outputs.files files([pomFile, artifactFile]) + doLast { + pom { + setGroupId(mavenPackage.group) + setArtifactId(mavenPackage.artifact) + setVersion(mavenPackage.version) + setPackaging(mavenPackage.artifactType) + }.writeTo(pomFile.absolutePath) + Files.copy(mavenPackage.sourceFile.toPath(), + artifactFile.toPath(), + StandardCopyOption.COPY_ATTRIBUTES, + StandardCopyOption.REPLACE_EXISTING) + } + } + } + ) + return createPomTaskProvider + } + +task usage { + description "Display help text for this script" + doLast { + println helpText + } +} + +task install { + description ("Install the artifacts specified by FILE_TO_MAVEN_PACKAGE in " + + "the local Maven repository located in TARGET_DIR. If an " + + "artifact is found in a remote Maven repository, it will not " + + "installed in the local Maven repository. Instead, the " + + "location of the artifact be printed in this task's summary.") + dependsOn createMavenPackageTasks + + doLast { + List sortedResults = createMavenPackageTasks.collect { + it.get().ext.has("mavenPackage") ? it.get().ext.mavenPackage : null + }.findAll { it != null }.sort { + it.repo.toString() + it.toString() + } + if (sortedResults) { + println "Packages:" + sortedResults.each { + println sprintf("%s %s", it, it.repo) + } + } + } +} + +task updateGoogleMavenIndex { + description "Create / update the Google Maven index in MAVEN_INDEXES_DIR" + + outputs.files files(MavenPackage.googleMavenIndexerInstance.indexFile) + + doLast { + File indexFile = MavenPackage.updateGoogleMavenIndex() + if (!indexFile) { + throw new GradleException( + sprintf("Failed to update Google Maven index %s", indexFile)) + } + logger.info(sprintf("Updated Google Maven index %s", indexFile)) + } +} + +task searchMavenIndexes { + description ("Search Maven indexes in for artifacts specified by " + + "FILE_TO_MAVEN_PACKAGE.") + + doLast { + List searchResults = project.ext.mavenPackages.collect { + return it.search(project.ext.mavenSearchUris) ?: it + } + List found = searchResults.findAll { it.repo } + if (found) { + println "Found artifacts:" + found.each { + println sprintf("%s --> %s (%s)", it.sourceFile, it, it.repo) + } + } + List missing = searchResults.findAll { it.repo == null } + if (missing) { + println "Missing artifacts:" + missing.each { + println sprintf("%s", it.sourceFile) + } + } + } +} + +project.defaultTasks = [install.name] diff --git a/source/PlayServicesResolver/scripts/generate_test_maven_package.sh b/source/AndroidResolver/scripts/generate_test_maven_package.sh similarity index 100% rename from source/PlayServicesResolver/scripts/generate_test_maven_package.sh rename to source/AndroidResolver/scripts/generate_test_maven_package.sh diff --git a/source/PlayServicesResolver/scripts/gradle-template.zip b/source/AndroidResolver/scripts/gradle-template.zip similarity index 98% rename from source/PlayServicesResolver/scripts/gradle-template.zip rename to source/AndroidResolver/scripts/gradle-template.zip index 18dd98bc..db181f66 100644 Binary files a/source/PlayServicesResolver/scripts/gradle-template.zip and b/source/AndroidResolver/scripts/gradle-template.zip differ diff --git a/source/AndroidResolver/scripts/settings.gradle b/source/AndroidResolver/scripts/settings.gradle new file mode 100644 index 00000000..693f4278 --- /dev/null +++ b/source/AndroidResolver/scripts/settings.gradle @@ -0,0 +1,18 @@ +/* + * Copyright 2019 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +rootProject.name = "androidResolver" + diff --git a/source/PlayServicesResolver/src/AndroidAbis.cs b/source/AndroidResolver/src/AndroidAbis.cs similarity index 84% rename from source/PlayServicesResolver/src/AndroidAbis.cs rename to source/AndroidResolver/src/AndroidAbis.cs index 031f20cf..20b12c3d 100644 --- a/source/PlayServicesResolver/src/AndroidAbis.cs +++ b/source/AndroidResolver/src/AndroidAbis.cs @@ -34,7 +34,7 @@ private class PropertyConfiguration { public enum Mode { FatOnly, // Only supports fat (armeabi-v7a & x86) builds. OneOfArmX86Fat, // Supports one of armeabi-v7a, x86 or fat builds. - AnyOfArmX86Arm64, // Supports any combination of armeabi-v7a, x86 or arm64 builds. + AnyOfArmX86Arm64, // Supports any combination of armeabi-v7a, x86, x86_64 or arm64 builds. } /// @@ -70,11 +70,21 @@ public PropertyConfiguration() { "UnityEditor.AndroidArchitecture"); if (Property != null && EnumType != null) { SelectionMode = Mode.AnyOfArmX86Arm64; - SupportedAbiToAbiEnumValue = new Dictionary() { - {"armeabi-v7a", "ARMv7"}, - {"arm64-v8a", "ARM64"}, - {"x86", "X86"} - }; + var supportedAbis = new Dictionary(); + foreach (var abi in new Dictionary() { + {"armeabi-v7a", "ARMv7"}, + {"arm64-v8a", "ARM64"}, + {"x86", "X86"}, + {"x86_64", "X86_64"} + }) { + try { + EnumValueStringToULong(EnumType, abi.Value); + supportedAbis[abi.Key] = abi.Value; + } catch (ArgumentException) { + // Ignore unsupported ABIs. + } + } + SupportedAbiToAbiEnumValue = supportedAbis; return; } // Unity 5.0 and above should support targetDevice. @@ -96,9 +106,20 @@ public PropertyConfiguration() { {"x86", ""} }; } - } - private static PropertyConfiguration propertyConfiguration = null; + // Backing store for the Instance property. + private static PropertyConfiguration instance = null; + + // Get the property configuration singleton. + public static PropertyConfiguration Instance { + get { + lock (typeof(PropertyConfiguration)) { + if (instance == null) instance = new PropertyConfiguration(); + } + return instance; + } + } + } /// /// Set of selected ABIs. @@ -109,9 +130,6 @@ public PropertyConfiguration() { /// Create the default ABI set. /// public AndroidAbis() { - lock (typeof(AndroidAbis)) { - if (propertyConfiguration == null) propertyConfiguration = new PropertyConfiguration(); - } abis = new HashSet(Supported); } @@ -126,7 +144,7 @@ public AndroidAbis(IEnumerable abisSet) { /// /// Create a set of ABIs from a comma separated set of ABI strings. /// - /// Set of ABI strings. + /// Comma separated set of ABI strings. public AndroidAbis(string abiString) { if (String.IsNullOrEmpty(abiString)) { abis = new HashSet(Supported); @@ -173,10 +191,10 @@ public override bool Equals(System.Object obj) { /// Get the supported set of Android ABIs for the current Unity version. /// The dictionary maps the official Android ABI name (i.e the directory name looked up by the /// operating system) to the UnityEditor.AndroidTargetDevice (Unity 5.x & 2017.x) or - // UnityEditor.AndroidArchitecture enumeration value name. + /// UnityEditor.AndroidArchitecture enumeration value name. /// private static Dictionary SupportedAbiToAbiEnumValue { - get { return propertyConfiguration.SupportedAbiToAbiEnumValue; } + get { return PropertyConfiguration.Instance.SupportedAbiToAbiEnumValue; } } /// @@ -204,10 +222,10 @@ public static IEnumerable AllSupported { /// /// Enum object to convert. private static ulong EnumValueObjectToULong(object enumValueObject) { - /// Flags enum values can't be cast directly to an integral type, however it is possible to - /// print the value as an integer string so convert to a string and then parse as an int. - /// Enums are considered unsigned by the formatter, so if an enum is defined as -1 it will - /// be formatted as UInt32.MaxValue, i.e. 4294967295. + // Flags enum values can't be cast directly to an integral type, however it is possible to + // print the value as an integer string so convert to a string and then parse as an int. + // Enums are considered unsigned by the formatter, so if an enum is defined as -1 it will + // be formatted as UInt32.MaxValue, i.e. 4294967295. return UInt64.Parse(String.Format("{0:D}", enumValueObject)); } @@ -222,11 +240,13 @@ private static ulong EnumValueStringToULong(Type enumType, string enumValueStrin /// /// Get / set the target device ABI (Unity >= 5.0.x) + /// Unity >= 2019.4 supports armeabi-v7a, arm64-v8a, x86, x86_64 & fat (i.e armeabi-v7a, arm64, x86, x86_64) /// Unity >= 2017.4 supports armeabi-v7a, arm64-v8a, x86 & fat (i.e armeabi-v7a, arm64, x86) - /// Unity >= 5.0.x & <= 2017.3 only support armeabi-v7a, x86 & fat (i.e armeabi-v7a & x86) + /// Unity >= 5.0.x & <= 2017.3 only support armeabi-v7a, x86 & fat (i.e armeabi-v7a & x86) /// public static AndroidAbis Current { set { + var propertyConfiguration = PropertyConfiguration.Instance; var property = propertyConfiguration.Property; var enumType = propertyConfiguration.EnumType; var supportedAbis = SupportedAbiToAbiEnumValue; @@ -266,6 +286,7 @@ public static AndroidAbis Current { } get { + var propertyConfiguration = PropertyConfiguration.Instance; var property = propertyConfiguration.Property; var enumType = propertyConfiguration.EnumType; var supportedAbis = SupportedAbiToAbiEnumValue; diff --git a/source/PlayServicesResolver/src/AndroidSdkManager.cs b/source/AndroidResolver/src/AndroidSdkManager.cs similarity index 94% rename from source/PlayServicesResolver/src/AndroidSdkManager.cs rename to source/AndroidResolver/src/AndroidSdkManager.cs index 7b9311c6..28df9b04 100644 --- a/source/PlayServicesResolver/src/AndroidSdkManager.cs +++ b/source/AndroidResolver/src/AndroidSdkManager.cs @@ -404,6 +404,8 @@ internal class SdkManagerUtil { /// Called when the query is complete. public static void QueryPackages(string toolPath, string toolArguments, Action complete) { + PlayServicesResolver.analytics.Report("/androidsdkmanager/querypackages", + "Android SDK Manager: Query Packages"); var window = CommandLineDialog.CreateCommandLineDialog( "Querying Android SDK packages"); PlayServicesResolver.Log(String.Format("Query Android SDK packages\n" + @@ -420,7 +422,14 @@ public static void QueryPackages(string toolPath, string toolArguments, toolPath, toolArguments, (CommandLine.Result result) => { window.Close(); - if (result.exitCode != 0) { + if (result.exitCode == 0) { + PlayServicesResolver.analytics.Report( + "/androidsdkmanager/querypackages/success", + "Android SDK Manager: Query Packages Succeeded"); + } else { + PlayServicesResolver.analytics.Report( + "/androidsdkmanager/querypackages/failed", + "Android SDK Manager: Query Packages Failed"); PlayServicesResolver.Log(String.Format(PACKAGES_MISSING, result.message)); } complete(result); @@ -446,6 +455,8 @@ public static void InstallPackages( HashSet packages, string licenseQuestion, string licenseAgree, string licenseDecline, Regex licenseTextHeader, Action complete) { + PlayServicesResolver.analytics.Report("/androidsdkmanager/installpackages", + "Android SDK Manager: Install Packages"); PlayServicesResolver.Log(String.Format("Install Android SDK packages\n" + "\n" + "{0} {1}\n", @@ -498,7 +509,7 @@ public static void InstallPackages( /// Tool that was executed. /// Arguments to passed to the tool. /// Whether the command is retrieving licenses. - /// List of package versions to install / upgrade.. + /// List of package versions to install / upgrade. /// Result of the tool's execution. private static void LogInstallLicenseResult( string toolPath, string toolArguments, bool retrievingLicenses, @@ -520,6 +531,20 @@ private static void LogInstallLicenseResult( AndroidSdkPackageNameVersion.ListToString(packages), toolResult.message), level: succeeded ? LogLevel.Info : LogLevel.Warning); + var analyticsParameters = new KeyValuePair[] { + new KeyValuePair( + "numPackages", + new List(packages).Count.ToString()) + }; + if (succeeded) { + PlayServicesResolver.analytics.Report( + "/androidsdkmanager/installpackages/success", analyticsParameters, + "Android SDK Manager: Install Packages Successful"); + } else { + PlayServicesResolver.analytics.Report( + "/androidsdkmanager/installpackages/failed", analyticsParameters, + "Android SDK Manager: Install Packages Failed"); + } } } @@ -912,7 +937,7 @@ public void InstallPackages(HashSet packages, var packagesString = AndroidSdkPackageNameVersion.ListToString(packages); // TODO: Remove this dialog when the package manager provides feedback while // downloading. - bool installPackage = UnityEditor.EditorUtility.DisplayDialog( + DialogWindow.Display( "Missing Android SDK packages", String.Format( "Android SDK packages need to be installed:\n" + @@ -922,19 +947,22 @@ public void InstallPackages(HashSet packages, "which may lead you to think Unity has hung / crashed. Would you like " + "to wait for these package to be installed?", packagesString), - "Yes", cancel: "No"); - if (!installPackage) { - PlayServicesResolver.Log( - "User cancelled installation of Android SDK tools package.", - level: LogLevel.Warning); - complete(false); - return; - } - var packageNames = new List(); - foreach (var pkg in packages) packageNames.Add(pkg.Name); - SdkManagerUtil.InstallPackages(toolPath, String.Join(" ", packageNames.ToArray()), - packages, "Accept? (y/N):", "y", "N", - new Regex("^License\\W+[^ ]+:"), complete); + DialogWindow.Option.Selected0, "Yes", "No", + complete: (selectedOption) => { + if (selectedOption == DialogWindow.Option.Selected0) { + PlayServicesResolver.Log( + "User cancelled installation of Android SDK tools package.", + level: LogLevel.Warning); + complete(false); + return; + } + var packageNames = new List(); + foreach (var pkg in packages) packageNames.Add(pkg.Name); + SdkManagerUtil.InstallPackages(toolPath, + String.Join(" ", packageNames.ToArray()), + packages, "Accept? (y/N):", "y", "N", + new Regex("^License\\W+[^ ]+:"), complete); + }); } } @@ -947,7 +975,7 @@ internal class AndroidSdkManager { /// /// Name of the tool to search for. /// SDK path to search for the tool. If this is null or empty, the - // system path is searched instead. + /// system path is searched instead. /// String with the path to the tool if found, null otherwise. private static string FindAndroidSdkTool(string toolName, string sdkPath = null) { if (String.IsNullOrEmpty(sdkPath)) { diff --git a/source/PlayServicesResolver/src/AndroidXmlDependencies.cs b/source/AndroidResolver/src/AndroidXmlDependencies.cs similarity index 92% rename from source/PlayServicesResolver/src/AndroidXmlDependencies.cs rename to source/AndroidResolver/src/AndroidXmlDependencies.cs index f4f5c31e..5af00803 100644 --- a/source/PlayServicesResolver/src/AndroidXmlDependencies.cs +++ b/source/AndroidResolver/src/AndroidXmlDependencies.cs @@ -58,6 +58,7 @@ protected override bool Read(string filename, Logger logger) { string group = null; string artifact = null; string versionSpec = null; + string classifier = null; List repositories = null; logger.Log( String.Format("Reading Android dependency XML file {0}", filename), @@ -79,12 +80,15 @@ protected override bool Read(string filename, Logger logger) { group = null; artifact = null; versionSpec = null; + classifier = null; repositories = new List(); // Parse a package specification in the form: // group:artifact:version_spec + // (or) + // group:artifact:version_spec:classifier var spec = reader.GetAttribute("spec") ?? ""; var specComponents = spec.Split(new [] { ':' }); - if (specComponents.Length != 3) { + if (specComponents.Length != 3 && specComponents.Length != 4) { logger.Log( String.Format( "Ignoring invalid package specification '{0}' " + @@ -96,11 +100,15 @@ protected override bool Read(string filename, Logger logger) { group = specComponents[0]; artifact = specComponents[1]; versionSpec = specComponents[2]; + if (specComponents.Length == 4) + classifier = specComponents[3]; + return true; } else if (!(String.IsNullOrEmpty(group) || String.IsNullOrEmpty(artifact) || - String.IsNullOrEmpty(versionSpec))) { - svcSupport.DependOn(group, artifact, versionSpec, + String.IsNullOrEmpty(versionSpec) + )) { + svcSupport.DependOn(group, artifact, versionSpec, classifier: classifier, packageIds: androidSdkPackageIds.ToArray(), repositories: repositories.ToArray(), createdBy: String.Format("{0}:{1}", filename, @@ -139,7 +147,9 @@ protected override bool Read(string filename, Logger logger) { } return true; } - return false; + // Ignore unknown tags so that different configurations can be stored in the + // same file. + return true; })) { return false; } diff --git a/source/PlayServicesResolver/src/CommandLine.cs b/source/AndroidResolver/src/CommandLine.cs similarity index 90% rename from source/PlayServicesResolver/src/CommandLine.cs rename to source/AndroidResolver/src/CommandLine.cs index 49375e79..8ec83985 100644 --- a/source/PlayServicesResolver/src/CommandLine.cs +++ b/source/AndroidResolver/src/CommandLine.cs @@ -19,6 +19,7 @@ namespace GooglePlayServices using System.Collections.Generic; using System.Diagnostics; using System.IO; + using System.Text; using System.Text.RegularExpressions; using System.Threading; using System; @@ -220,7 +221,7 @@ private void Read() byte[] copy = new byte[bytesRead]; Array.Copy(buffer, copy, copy.Length); DataReceived(new StreamData( - handle, System.Text.Encoding.UTF8.GetString(copy), copy, + handle, Encoding.UTF8.GetString(copy), copy, complete)); } } @@ -374,7 +375,7 @@ public LineReader(IOHandler handler = null) /// a newline isn't present. /// /// Handle of the stream to query. - /// List of data for the requested stream. + /// List of data for the requested stream. public List GetBufferedData(int handle) { List handleData; @@ -396,7 +397,6 @@ public void Flush() /// /// Aggregate the specified list of StringBytes into a single structure. /// - /// Stream handle. /// Data to aggregate. public static StreamData Aggregate(List dataStream) { @@ -511,6 +511,11 @@ public static Result Run(string toolPath, string arguments, string workingDirect envVars: envVars, ioHandler: ioHandler, useShellExecution: false); } + /// + /// Whether the console configuration warning has been displayed. + /// + private static bool displayedConsoleConfigurationWarning = false; + /// /// Execute a command line tool. /// @@ -530,26 +535,40 @@ public static Result Run(string toolPath, string arguments, string workingDirect /// executing a command via the shell. This requires: /// * cmd.exe (on Windows) or bash (on OSX / Linux) are in the path. /// * Arguments containing whitespace are quoted. + /// Causes environment variable LANG to be explicitly set + /// when executing a command via the shell. This is only applicable to OSX. /// CommandLineTool result if successful, raises an exception if it's not /// possible to execute the tool. public static Result RunViaShell( string toolPath, string arguments, string workingDirectory = null, Dictionary envVars = null, IOHandler ioHandler = null, bool useShellExecution = false, - bool stdoutRedirectionInShellMode = true) { - System.Text.Encoding inputEncoding = Console.InputEncoding; - System.Text.Encoding outputEncoding = Console.OutputEncoding; + bool stdoutRedirectionInShellMode = true, bool setLangInShellMode = false) { + bool consoleConfigurationWarning = false; + Encoding inputEncoding = Console.InputEncoding; + Encoding outputEncoding = Console.OutputEncoding; // Android SDK manager requires the input encoding to be set to // UFT8 to pipe data to the tool. // Cocoapods requires the output encoding to be set to UTF8 to // retrieve output. try { - Console.InputEncoding = System.Text.Encoding.UTF8; - Console.OutputEncoding = System.Text.Encoding.UTF8; + var utf8Type = Encoding.UTF8.GetType(); + // Only compare the type of the encoding class since the configuration shouldn't + // matter. + if (inputEncoding.GetType() != utf8Type) { + Console.InputEncoding = Encoding.UTF8; + } + if (outputEncoding.GetType() != utf8Type) { + Console.OutputEncoding = Encoding.UTF8; + } } catch (Exception e) { - UnityEngine.Debug.LogWarning(String.Format( - "Unable to set console input / output encoding to UTF8 (e.g en_US.UTF8-8). " + - "Some commands may fail. {0}", e)); + if (!displayedConsoleConfigurationWarning) { + UnityEngine.Debug.LogWarning(String.Format( + "Unable to set console input / output encoding from {0} & {1} to {2} " + + "(e.g en_US.UTF8-8). Some commands may fail. {3}", + inputEncoding, outputEncoding, Encoding.UTF8, e)); + consoleConfigurationWarning = true; + } } // Mono 3.x on Windows can't execute tools with single quotes (apostrophes) in the path. @@ -559,6 +578,10 @@ public static Result RunViaShell( toolPath.Contains("\'")) { useShellExecution = true; stdoutRedirectionInShellMode = true; + } else if (!(toolPath.StartsWith("\"") || toolPath.StartsWith("'"))) { + // If the path isn't quoted normalize separators. + // Windows can't execute commands using POSIX paths. + toolPath = FileUtils.NormalizePathSeparators(toolPath); } string stdoutFileName = null; @@ -570,6 +593,7 @@ public static Result RunViaShell( string shellArgPrefix; string shellArgPostfix; string escapedToolPath = toolPath; + string additionalCommands = ""; if (UnityEngine.RuntimePlatform.WindowsEditor == UnityEngine.Application.platform) { shellCmd = "cmd.exe"; shellArgPrefix = "/c \""; @@ -579,9 +603,13 @@ public static Result RunViaShell( shellArgPrefix = "-l -c '"; shellArgPostfix = "'"; escapedToolPath = toolPath.Replace("'", "'\\''"); + if (UnityEngine.RuntimePlatform.OSXEditor == UnityEngine.Application.platform + && envVars != null && envVars.ContainsKey("LANG") && setLangInShellMode) { + additionalCommands = String.Format("export {0}={1}; ", "LANG", envVars["LANG"]); + } } - arguments = String.Format("{0}\"{1}\" {2} 1> {3} 2> {4}{5}", shellArgPrefix, - escapedToolPath, arguments, stdoutFileName, + arguments = String.Format("{0}{1}\"{2}\" {3} 1> {4} 2> {5}{6}", shellArgPrefix, + additionalCommands, escapedToolPath, arguments, stdoutFileName, stderrFileName, shellArgPostfix); toolPath = shellCmd; } @@ -650,8 +678,22 @@ public static Result RunViaShell( result.exitCode = process.ExitCode; result.message = FormatResultMessage(toolPath, arguments, result.stdout, result.stderr, result.exitCode); - Console.InputEncoding = inputEncoding; - Console.OutputEncoding = outputEncoding; + try { + if (Console.InputEncoding != inputEncoding) { + Console.InputEncoding = inputEncoding; + } + if (Console.OutputEncoding != outputEncoding) { + Console.OutputEncoding = outputEncoding; + } + } catch (Exception e) { + if (!displayedConsoleConfigurationWarning) { + UnityEngine.Debug.LogWarning(String.Format( + "Unable to restore console input / output encoding to {0} & {1}. {2}", + inputEncoding, outputEncoding, e)); + consoleConfigurationWarning = true; + } + } + if (consoleConfigurationWarning) displayedConsoleConfigurationWarning = true; return result; } diff --git a/source/PlayServicesResolver/src/CommandLineDialog.cs b/source/AndroidResolver/src/CommandLineDialog.cs similarity index 98% rename from source/PlayServicesResolver/src/CommandLineDialog.cs rename to source/AndroidResolver/src/CommandLineDialog.cs index 6110a34b..570d45cb 100644 --- a/source/PlayServicesResolver/src/CommandLineDialog.cs +++ b/source/AndroidResolver/src/CommandLineDialog.cs @@ -230,6 +230,7 @@ public override void Initialize() progressSummary = ""; UpdateEvent = null; autoScrollToBottom = false; + logRedirector.ShouldLogDelegate = () => { return !RunningCommand; }; } /// @@ -253,11 +254,12 @@ protected override void OnGUI() { EditorGUILayout.BeginVertical(); EditorGUILayout.LabelField(progressTitle, EditorStyles.boldLabel); var progressBarRect = EditorGUILayout.BeginVertical(); + float progressValue = Math.Min(progress, 1.0f); EditorGUILayout.LabelField(""); // Creates vertical space for the progress bar. EditorGUI.ProgressBar( - progressBarRect, progress, + progressBarRect, progressValue, String.IsNullOrEmpty(progressSummary) ? - String.Format("{0}%... ", (int)(progress * 100.0f)) : progressSummary); + String.Format("{0}%... ", (int)(progressValue * 100.0f)) : progressSummary); EditorGUILayout.EndVertical(); EditorGUILayout.Space(); EditorGUILayout.EndVertical(); diff --git a/source/AndroidResolver/src/EmbeddedResource.cs b/source/AndroidResolver/src/EmbeddedResource.cs new file mode 100644 index 00000000..f9f64be2 --- /dev/null +++ b/source/AndroidResolver/src/EmbeddedResource.cs @@ -0,0 +1,167 @@ +// +// Copyright (C) 2019 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Google { + using System; + using System.Collections.Generic; + using System.IO; + using System.Reflection; + + /// + /// Methods to manage assembly embedded resources. + /// + internal static class EmbeddedResource { + + /// + /// Extracted resource file information. + /// + private class ExtractedResource { + + /// + /// Assembly that contains the resource. + /// + private Assembly assembly; + + /// + /// Assembly modification time. + /// + private DateTime assemblyModificationTime; + + /// + /// Name of the extracted resource. + /// + private string resourceName; + + /// + /// Path to the extracted resource. + /// + private string path; + + /// + /// Construct an object to track an extracted resource. + /// + /// Assembly the resource was extracted from. + /// Last time the source assembly file was + /// modified. + /// Name of the resource in the assembly. + /// Path of the extracted resource. + public ExtractedResource(Assembly assembly, DateTime assemblyModificationTime, + string resourceName, string path) { + this.assembly = assembly; + this.assemblyModificationTime = assemblyModificationTime; + this.resourceName = resourceName; + this.path = path; + } + + /// + /// Name of the extracted resource. + /// + public string ResourceName { get { return resourceName; } } + + /// + /// Path of the extracted file. + /// + public string Path { get { return path; } } + + /// + /// Whether the extracted file is out of date. + /// + public bool OutOfDate { + get { + // If the source assembly is newer than the extracted file, the extracted file + // is out of date. + return !File.Exists(path) || + assemblyModificationTime.CompareTo(File.GetLastWriteTime(path)) > 0; + } + } + + /// + /// Extract the embedded resource to the Path creating intermediate directories + /// if they're required. + /// + /// Logger to log messages to. + /// true if successful, false otherwise. + public bool Extract(Logger logger) { + if (OutOfDate) { + var stream = assembly.GetManifestResourceStream(resourceName); + if (stream == null) { + logger.Log( + String.Format("Failed to find resource {0} in assembly {1}", + ResourceName, assembly.FullName), + level: LogLevel.Error); + return false; + } + var data = new byte[stream.Length]; + stream.Read(data, 0, (int)stream.Length); + try { + Directory.CreateDirectory(System.IO.Path.GetDirectoryName( + FileUtils.NormalizePathSeparators(Path))); + File.WriteAllBytes(Path, data); + } catch (IOException error) { + logger.Log( + String.Format("Failed to write resource {0} from assembly {1} to {2} " + + "({3})", ResourceName, assembly.FullName, Path, error), + level: LogLevel.Error); + return false; + } + } + return true; + } + } + + // Cache of extracted resources by path. This is used to avoid file operations when + // checking to see whether resources have already been extracted or are out of date. + private static Dictionary extractedResourceByPath = + new Dictionary(); + + /// + /// Extract a list of embedded resources to the specified path creating intermediate + /// directories if they're required. + /// + /// Assembly to extract resources from. + /// Each Key is the resource to extract and each + /// Value is the path to extract to. If the resource name (Key) is null or empty, this + /// method will attempt to extract a resource matching the filename component of the path. + /// + /// true if successful, false otherwise. + public static bool ExtractResources( + Assembly assembly, + IEnumerable> resourceNameToTargetPaths, + Logger logger) { + bool allResourcesExtracted = true; + var assemblyModificationTime = File.GetLastWriteTime(assembly.Location); + foreach (var resourceNameToTargetPath in resourceNameToTargetPaths) { + var targetPath = FileUtils.PosixPathSeparators( + Path.GetFullPath(resourceNameToTargetPath.Value)); + var resourceName = resourceNameToTargetPath.Key; + if (String.IsNullOrEmpty(resourceName)) { + resourceName = Path.GetFileName(FileUtils.NormalizePathSeparators(targetPath)); + } + ExtractedResource resourceToExtract; + if (!extractedResourceByPath.TryGetValue(targetPath, out resourceToExtract)) { + resourceToExtract = new ExtractedResource(assembly, assemblyModificationTime, + resourceName, targetPath); + } + if (resourceToExtract.Extract(logger)) { + extractedResourceByPath[targetPath] = resourceToExtract; + } else { + allResourcesExtracted = false; + } + } + return allResourcesExtracted; + } + } +} diff --git a/source/AndroidResolver/src/GradleResolver.cs b/source/AndroidResolver/src/GradleResolver.cs new file mode 100644 index 00000000..c9f4eac1 --- /dev/null +++ b/source/AndroidResolver/src/GradleResolver.cs @@ -0,0 +1,1443 @@ +// +// Copyright (C) 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace GooglePlayServices +{ + using UnityEngine; + using UnityEditor; + using System.Collections.Generic; + using System.Collections.Specialized; + using Google; + using Google.JarResolver; + using System; + using System.Collections; + using System.IO; + using System.Text.RegularExpressions; + using System.Xml; + + public class GradleResolver + { + public GradleResolver() {} + + /// + /// Parse output of download_artifacts.gradle into lists of copied and missing artifacts. + /// + /// Standard output of the download_artifacts.gradle. + /// Directory to artifacts were copied into. + /// Returns a list of copied artifact files. + /// Returns a list of missing artifact + /// specifications. + /// Returns a list of artifact specifications that were + /// modified. + private void ParseDownloadGradleArtifactsGradleOutput( + string output, string destinationDirectory, + out List copiedArtifacts, out List missingArtifacts, + out List modifiedArtifacts) { + // Parse stdout for copied and missing artifacts. + copiedArtifacts = new List(); + missingArtifacts = new List(); + modifiedArtifacts = new List(); + string currentHeader = null; + const string COPIED_ARTIFACTS_HEADER = "Copied artifacts:"; + const string MISSING_ARTIFACTS_HEADER = "Missing artifacts:"; + const string MODIFIED_ARTIFACTS_HEADER = "Modified artifacts:"; + foreach (var line in output.Split(new string[] { "\r\n", "\n" }, + StringSplitOptions.None)) { + if (line.StartsWith(COPIED_ARTIFACTS_HEADER) || + line.StartsWith(MISSING_ARTIFACTS_HEADER) || + line.StartsWith(MODIFIED_ARTIFACTS_HEADER)) { + currentHeader = line; + continue; + } else if (String.IsNullOrEmpty(line.Trim())) { + currentHeader = null; + continue; + } + if (!String.IsNullOrEmpty(currentHeader)) { + if (currentHeader == COPIED_ARTIFACTS_HEADER) { + // Store the POSIX path of the copied package to handle Windows + // path variants. + copiedArtifacts.Add( + FileUtils.PosixPathSeparators( + Path.Combine(destinationDirectory, line.Trim()))); + } else if (currentHeader == MISSING_ARTIFACTS_HEADER) { + missingArtifacts.Add(line.Trim()); + } else if (currentHeader == MODIFIED_ARTIFACTS_HEADER) { + modifiedArtifacts.Add(line.Trim()); + } + } + } + } + + /// + /// Log an error with the set of dependencies that were not fetched. + /// + /// List of missing dependencies. + private void LogMissingDependenciesError(List missingArtifacts) { + // Log error for missing packages. + if (missingArtifacts.Count > 0) { + PlayServicesResolver.analytics.Report( + "/resolve/gradle/failed", + PlayServicesResolver.GetResolutionMeasurementParameters(missingArtifacts), + "Gradle Resolve Failed"); + PlayServicesResolver.Log( + String.Format("Resolution failed\n\n" + + "Failed to fetch the following dependencies:\n{0}\n\n", + String.Join("\n", missingArtifacts.ToArray())), + level: LogLevel.Error); + } else { + PlayServicesResolver.analytics.Report( + "/resolve/gradle/failed", + PlayServicesResolver.GetResolutionMeasurementParameters(null), + "Gradle Resolve Failed"); + } + } + + /// + /// Get package spec from a dependency. + /// + /// Dependency instance to query for package spec. + internal static string DependencyToPackageSpec(Dependency dependency) { + return dependency.Version.ToUpper() == "LATEST" ? + dependency.VersionlessKey + ":+" : dependency.Key; + } + + /// + /// From a list of dependencies generate a list of Maven / Gradle / Ivy package spec + /// strings. + /// + /// Dependency instances to query for package specs. + /// Dictionary of Dependency instances indexed by package spec strings. + internal static Dictionary DependenciesToPackageSpecs( + IEnumerable dependencies) { + var sourcesByPackageSpec = new Dictionary(); + foreach (var dependency in dependencies) { + // Convert the legacy "LATEST" version spec to a Gradle version spec. + var packageSpec = DependencyToPackageSpec(dependency); + var source = CommandLine.SplitLines(dependency.CreatedBy)[0]; + string sources; + if (sourcesByPackageSpec.TryGetValue(packageSpec, out sources)) { + sources = sources + ", " + source; + } else { + sources = source; + } + sourcesByPackageSpec[packageSpec] = sources; + } + return sourcesByPackageSpec; + } + + /// + /// Convert a repo path to a valid URI. + /// If the specified repo is a local directory and it doesn't exist, search the project + /// for a match. + /// Valid paths are: + /// * Path relative to Assets or Packages folder, ex. "Firebase/m2repository" + /// * Path relative to project folder, ex."Assets/Firebase/m2repository" + /// + /// Repo path to convert. + /// XML or source file this path is referenced from. If this is + /// null the calling method's source location is used when logging the source of this + /// repo declaration. + /// URI to the repo. + internal static string RepoPathToUri(string repoPath, string sourceLocation=null) { + if (sourceLocation == null) { + // Get the caller's stack frame. + sourceLocation = System.Environment.StackTrace.Split(new char[] { '\n' })[1]; + } + // Filter Android SDK repos as they're supplied in the build script. + if (repoPath.StartsWith(PlayServicesSupport.SdkVariable)) return null; + // Since we need a URL, determine whether the repo has a scheme. If not, + // assume it's a local file. + foreach (var scheme in new [] { "file:", "http:", "https:" }) { + if (repoPath.StartsWith(scheme)) return GradleWrapper.EscapeUri(repoPath); + } + + if (!Directory.Exists(repoPath)) { + string trimmedRepoPath = repoPath; + string foundPath = ""; + bool shouldLog = false; + + if (FileUtils.IsUnderDirectory(repoPath, FileUtils.ASSETS_FOLDER)) { + trimmedRepoPath = repoPath.Substring(FileUtils.ASSETS_FOLDER.Length + 1); + } else if (FileUtils.IsUnderPackageDirectory(repoPath)) { + // Trim the Packages/package-id/ part + string packageFolder = FileUtils.GetPackageDirectory(repoPath); + if (!String.IsNullOrEmpty(packageFolder)) { + trimmedRepoPath = repoPath.Substring(packageFolder.Length + 1); + } + } + + // Search under Packages/package-id first if Dependencies.xml is from a UPM package. + if (FileUtils.IsUnderPackageDirectory(sourceLocation)) { + // Get the physical package directory. + // Ex. Library/PackageCache/com.google.unity-jar-resolver@1.2.120/ + string packageFolder = FileUtils.GetPackageDirectory( + sourceLocation, FileUtils.PackageDirectoryType.PhysicalPath); + if (!String.IsNullOrEmpty(packageFolder)) { + string repoPathUnderPackages = + packageFolder + Path.DirectorySeparatorChar + trimmedRepoPath; + if (Directory.Exists(repoPathUnderPackages)) { + foundPath = repoPathUnderPackages; + } else { + // It is unlikely but possible the user has moved the repository in the + // project under Packages directory, so try searching for it. + foundPath = FileUtils.FindPathUnderDirectory( + packageFolder, trimmedRepoPath); + if (!String.IsNullOrEmpty(foundPath)) { + foundPath = packageFolder + Path.DirectorySeparatorChar + foundPath; + shouldLog = true; + } + } + } + } + + // Search under Assets/ + if (String.IsNullOrEmpty(foundPath)) { + // Try to find under "Assets" folder. It is possible that "Assets/" was not + // added to the repoPath. + string repoPathUnderAssets = + FileUtils.ASSETS_FOLDER + Path.DirectorySeparatorChar + trimmedRepoPath; + if (Directory.Exists(repoPathUnderAssets)) { + foundPath = repoPathUnderAssets; + } else { + // If the directory isn't found, it is possible the user has moved the + // repository in the project under Assets directory, so try searching for + // it. + foundPath = FileUtils.FindPathUnderDirectory( + FileUtils.ASSETS_FOLDER, trimmedRepoPath); + if (!String.IsNullOrEmpty(foundPath)) { + foundPath = FileUtils.ASSETS_FOLDER + + Path.DirectorySeparatorChar + foundPath; + shouldLog = true; + } + } + } + + if (!String.IsNullOrEmpty(foundPath)) { + if (shouldLog) { + PlayServicesResolver.Log(String.Format( + "{0}: Repo path '{1}' does not exist, will try using '{2}' instead.", + sourceLocation, repoPath, foundPath), level: LogLevel.Warning); + } + repoPath = foundPath; + } else { + PlayServicesResolver.Log(String.Format( + "{0}: Repo path '{1}' does not exist.", sourceLocation, repoPath), + level: LogLevel.Warning); + } + } + return GradleWrapper.EscapeUri(GradleWrapper.PathToFileUri(repoPath)); + } + + /// + /// Extract the ordered set of repository URIs from the specified dependencies. + /// + /// Dependency instances to query for repos. + /// Dictionary of source filenames by repo names. + internal static List> DependenciesToRepoUris( + IEnumerable dependencies) { + var sourcesByRepo = new OrderedDictionary(); + Action addToSourcesByRepo = (repo, source) => { + if (!String.IsNullOrEmpty(repo)) { + if (sourcesByRepo.Contains(repo)) { + var sources = (List)sourcesByRepo[repo]; + if (!sources.Contains(source)) { + sources.Add(source); + } + } else { + sourcesByRepo[repo] = new List() { source }; + } + } + }; + // Add global repos first. + foreach (var kv in PlayServicesSupport.AdditionalRepositoryPaths) { + addToSourcesByRepo(RepoPathToUri(kv.Key, sourceLocation: kv.Value), kv.Value); + } + // Build array of repos to search, they're interleaved across all dependencies as the + // order matters. + int maxNumberOfRepos = 0; + foreach (var dependency in dependencies) { + maxNumberOfRepos = Math.Max(maxNumberOfRepos, dependency.Repositories.Length); + } + for (int i = 0; i < maxNumberOfRepos; i++) { + foreach (var dependency in dependencies) { + var repos = dependency.Repositories; + if (i >= repos.Length) continue; + var createdBy = CommandLine.SplitLines(dependency.CreatedBy)[0]; + addToSourcesByRepo(RepoPathToUri(repos[i], sourceLocation: createdBy), + createdBy); + } + } + var sourcesByRepoList = new List>(); + var enumerator = sourcesByRepo.GetEnumerator(); + while (enumerator.MoveNext()) { + sourcesByRepoList.Add( + new KeyValuePair( + (string)enumerator.Key, + String.Join(", ", ((List)enumerator.Value).ToArray()))); + } + return sourcesByRepoList; + } + + // Holds Gradle resolution state. + private class ResolutionState { + public CommandLine.Result commandLineResult = new CommandLine.Result(); + public List copiedArtifacts = new List(); + public List missingArtifacts = new List(); + public List missingArtifactsAsDependencies = new List(); + public List modifiedArtifacts = new List(); + } + + /// + /// Perform resolution using Gradle. + /// + /// Directory to copy packages into. + /// Path to the Android SDK. This is required as + /// PlayServicesSupport.SDK can only be called from the main thread. + /// Log errors when artifacts are missing. + /// Whether to unconditionally close the resolution + /// window when complete. + /// Called when resolution is complete with a list of + /// packages that were not found. + private void GradleResolution( + string destinationDirectory, string androidSdkPath, + bool logErrorOnMissingArtifacts, bool closeWindowOnCompletion, + System.Action> resolutionComplete) { + // Get all dependencies. + var allDependencies = PlayServicesSupport.GetAllDependencies(); + var allDependenciesList = new List(allDependencies.Values); + + PlayServicesResolver.analytics.Report( + "/resolve/gradle", PlayServicesResolver.GetResolutionMeasurementParameters(null), + "Gradle Resolve"); + + var gradleWrapper = PlayServicesResolver.Gradle; + var buildScript = Path.GetFullPath(Path.Combine( + gradleWrapper.BuildDirectory, + PlayServicesResolver.EMBEDDED_RESOURCES_NAMESPACE + "download_artifacts.gradle")); + + // Extract the gradle wrapper and build script. + if (!(gradleWrapper.Extract(PlayServicesResolver.logger) && + EmbeddedResource.ExtractResources( + typeof(GradleResolver).Assembly, + new KeyValuePair[] { + new KeyValuePair(null, buildScript), + // Copies the settings.gradle file into this folder to mark it as a Gradle + // project. Without the settings.gradle file, Gradle will search up all + // parent directories for a settings.gradle and prevent execution of the + // download_artifacts.gradle script if a settings.gradle is found. + new KeyValuePair( + PlayServicesResolver.EMBEDDED_RESOURCES_NAMESPACE + "settings.gradle", + Path.GetFullPath(Path.Combine(gradleWrapper.BuildDirectory, + "settings.gradle"))), + }, PlayServicesResolver.logger))) { + PlayServicesResolver.analytics.Report("/resolve/gradle/failed/extracttools", + "Gradle Resolve: Tool Extraction Failed"); + PlayServicesResolver.Log(String.Format( + "Failed to extract {0} and {1} from assembly {2}", + gradleWrapper.Executable, buildScript, + typeof(GradleResolver).Assembly.FullName), + level: LogLevel.Error); + resolutionComplete(allDependenciesList); + return; + } + // Build array of repos to search, they're interleaved across all dependencies as the + // order matters. + var repoList = new List(); + foreach (var kv in DependenciesToRepoUris(allDependencies.Values)) repoList.Add(kv.Key); + + // Create an instance of ResolutionState to aggregate the results. + var resolutionState = new ResolutionState(); + + // Window used to display resolution progress. + var window = CommandLineDialog.CreateCommandLineDialog( + "Resolving Android Dependencies"); + + // Register an event to redirect log messages to the resolution window. + var logRedirector = window.Redirector; + PlayServicesResolver.logger.LogMessage += logRedirector.LogToWindow; + + // When resolution is complete unregister the log redirection event. + Action resolutionCompleteRestoreLogger = () => { + PlayServicesResolver.logger.LogMessage -= logRedirector.LogToWindow; + // If the command completed successfully or the log level is info or above close + // the window, otherwise leave it open for inspection. + if ((resolutionState.commandLineResult.exitCode == 0 && + PlayServicesResolver.logger.Level >= LogLevel.Info && + !(logRedirector.WarningLogged || logRedirector.ErrorLogged)) || + closeWindowOnCompletion) { + window.Close(); + } + resolutionComplete(resolutionState.missingArtifactsAsDependencies); + }; + + // Executed after refreshing the explode cache. + Action processAars = () => { + // Find all labeled files that were not copied and delete them. + var staleArtifacts = new HashSet(); + var copiedArtifactsSet = new HashSet(resolutionState.copiedArtifacts); + foreach (var assetPath in PlayServicesResolver.FindLabeledAssets()) { + if (!copiedArtifactsSet.Contains(FileUtils.PosixPathSeparators(assetPath))) { + staleArtifacts.Add(assetPath); + } + } + if (staleArtifacts.Count > 0) { + PlayServicesResolver.Log( + String.Format("Deleting stale dependencies:\n{0}", + String.Join("\n", + (new List(staleArtifacts)).ToArray())), + level: LogLevel.Verbose); + var deleteFailures = new List(); + foreach (var assetPath in staleArtifacts) { + deleteFailures.AddRange(FileUtils.DeleteExistingFileOrDirectory(assetPath)); + } + var deleteError = FileUtils.FormatError("Failed to delete stale artifacts", + deleteFailures); + if (!String.IsNullOrEmpty(deleteError)) { + PlayServicesResolver.Log(deleteError, level: LogLevel.Error); + } + } + // Process / explode copied AARs. + ProcessAars( + destinationDirectory, new HashSet(resolutionState.copiedArtifacts), + (progress, message) => { + window.SetProgress("Processing libraries...", progress, message); + }, + () => { + // Look up the original Dependency structure for each missing artifact. + resolutionState.missingArtifactsAsDependencies = new List(); + foreach (var artifact in resolutionState.missingArtifacts) { + Dependency dep; + if (!allDependencies.TryGetValue(artifact, out dep)) { + // If this fails, something may have gone wrong with the Gradle + // script. Rather than failing hard, fallback to recreating the + // Dependency class with the partial data we have now. + var components = new List( + artifact.Split(new char[] { ':' })); + if (components.Count < 2) { + PlayServicesResolver.Log( + String.Format( + "Found invalid missing artifact {0}\n" + + "Something went wrong with the gradle artifact " + + "download script\n." + + "Please report a bug", artifact), + level: LogLevel.Warning); + continue; + } else if (components.Count < 3 || components[2] == "+") { + components.Add("LATEST"); + } + dep = new Dependency(components[0], components[1], components[2]); + } + resolutionState.missingArtifactsAsDependencies.Add(dep); + } + if (logErrorOnMissingArtifacts) { + LogMissingDependenciesError(resolutionState.missingArtifacts); + } + resolutionCompleteRestoreLogger(); + }); + }; + + // Executed when Gradle finishes execution. + CommandLine.CompletionHandler gradleComplete = (commandLineResult) => { + resolutionState.commandLineResult = commandLineResult; + if (commandLineResult.exitCode != 0) { + PlayServicesResolver.analytics.Report("/resolve/gradle/failed/fetch", + "Gradle Resolve: Tool Extraction Failed"); + resolutionState.missingArtifactsAsDependencies = allDependenciesList; + PlayServicesResolver.Log( + String.Format("Gradle failed to fetch dependencies.\n\n{0}", + commandLineResult.message), level: LogLevel.Error); + resolutionCompleteRestoreLogger(); + return; + } + // Parse stdout for copied and missing artifacts. + ParseDownloadGradleArtifactsGradleOutput(commandLineResult.stdout, + destinationDirectory, + out resolutionState.copiedArtifacts, + out resolutionState.missingArtifacts, + out resolutionState.modifiedArtifacts); + // Display a warning about modified artifact versions. + if (resolutionState.modifiedArtifacts.Count > 0) { + PlayServicesResolver.Log( + String.Format( + "Some conflicting dependencies were found.\n" + + "The following dependency versions were modified:\n" + + "{0}\n", + String.Join("\n", resolutionState.modifiedArtifacts.ToArray())), + level: LogLevel.Warning); + } + // Label all copied files. + PlayServicesResolver.LabelAssets( + resolutionState.copiedArtifacts, + complete: (unusedUnlabeled) => { + // Check copied files for Jetpack (AndroidX) libraries. + if (PlayServicesResolver.FilesContainJetpackLibraries( + resolutionState.copiedArtifacts)) { + PlayServicesResolver.analytics.Report( + "/resolve/gradle/androidxdetected", + "Gradle Resolve: AndroidX detected"); + bool jetifierEnabled = SettingsDialog.UseJetifier; + SettingsDialog.UseJetifier = true; + // Make sure Jetpack is supported, prompting the user to configure Unity + // in a supported configuration. + PlayServicesResolver.CanEnableJetifierOrPromptUser( + "Jetpack (AndroidX) libraries detected, ", (useJetifier) => { + if (useJetifier) { + PlayServicesResolver.analytics.Report( + "/resolve/gradle/enablejetifier/enable", + "Gradle Resolve: Enable Jetifier"); + if (jetifierEnabled != SettingsDialog.UseJetifier) { + PlayServicesResolver.Log( + "Detected Jetpack (AndroidX) libraries, enabled " + + "the jetifier and resolving again."); + // Run resolution again with the Jetifier enabled. + PlayServicesResolver.DeleteLabeledAssets(); + GradleResolution(destinationDirectory, + androidSdkPath, + logErrorOnMissingArtifacts, + closeWindowOnCompletion, + resolutionComplete); + return; + } + processAars(); + } else { + PlayServicesResolver.analytics.Report( + "/resolve/gradle/enablejetifier/abort", + "Gradle Resolve: Enable Jetifier Aborted"); + // If the user didn't change their configuration, delete all + // resolved libraries and abort resolution as the build will + // fail. + PlayServicesResolver.DeleteLabeledAssets(); + resolutionState.missingArtifactsAsDependencies = + allDependenciesList; + resolutionCompleteRestoreLogger(); + return; + } + }); + } else { + // Successful, proceed with processing libraries. + processAars(); + } + }, + synchronous: false, + progressUpdate: (progress, message) => { + window.SetProgress("Labeling libraries...", progress, message); + }); + }; + + var packageSpecs = + new List(DependenciesToPackageSpecs(allDependencies.Values).Keys); + + var androidGradlePluginVersion = PlayServicesResolver.AndroidGradlePluginVersion; + // If this version of Unity doesn't support Gradle builds use a relatively + // recent (June 2019) version of the data binding library. + if (String.IsNullOrEmpty(androidGradlePluginVersion)) { + androidGradlePluginVersion = "2.3.0"; + } + var gradleProjectProperties = new Dictionary() { + { "ANDROID_HOME", androidSdkPath }, + { "TARGET_DIR", Path.GetFullPath(destinationDirectory) }, + { "MAVEN_REPOS", String.Join(";", repoList.ToArray()) }, + { "PACKAGES_TO_COPY", String.Join(";", packageSpecs.ToArray()) }, + { "USE_JETIFIER", SettingsDialog.UseJetifier ? "1" : "0" }, + { "DATA_BINDING_VERSION", androidGradlePluginVersion } + }; + + // Run the build script to perform the resolution popping up a window in the editor. + window.summaryText = "Resolving Android Dependencies..."; + window.modal = false; + window.progressTitle = window.summaryText; + window.autoScrollToBottom = true; + window.logger = PlayServicesResolver.logger; + var maxProgressLines = (allDependenciesList.Count * 10) + 50; + + if (gradleWrapper.Run( + SettingsDialog.UseGradleDaemon, buildScript, gradleProjectProperties, + null, PlayServicesResolver.logger, + (string toolPath, string arguments) => { + window.RunAsync( + toolPath, arguments, + (result) => { RunOnMainThread.Run(() => { gradleComplete(result); }); }, + workingDirectory: gradleWrapper.BuildDirectory, + maxProgressLines: maxProgressLines); + return true; + })) { + window.Show(); + } else { + resolutionComplete(allDependenciesList); + } + } + + /// + /// Search the project for AARs & JARs that could conflict with each other and resolve + /// the conflicts if possible. + /// + /// + /// This handles the following cases: + /// 1. If any libraries present match the name play-services-* and google-play-services.jar + /// is included in the project the user will be warned of incompatibility between + /// the legacy JAR and the newer AAR libraries. + /// 2. If a managed (labeled) library conflicting with one or more versions of unmanaged + /// (e.g play-services-base-10.2.3.aar (managed) vs. play-services-10.2.2.aar (unmanaged) + /// and play-services-base-9.2.4.aar (unmanaged)) + /// The user is warned about the unmanaged conflicting libraries and, if they're + /// older than the managed library, prompted to delete the unmanaged libraries. + /// + /// Called when the operation is complete. + private void FindAndResolveConflicts(Action complete) { + Func getVersionlessArtifactFilename = (filename) => { + var basename = Path.GetFileName(filename); + int split = basename.LastIndexOf("-"); + return split >= 0 ? basename.Substring(0, split) : basename; + }; + var managedPlayServicesArtifacts = new List(); + // Gather artifacts managed by the resolver indexed by versionless name. + var managedArtifacts = new Dictionary(); + var managedArtifactFilenames = new HashSet(); + foreach (var filename in PlayServicesResolver.FindLabeledAssets()) { + var artifact = getVersionlessArtifactFilename(filename); + managedArtifacts[artifact] = filename; + if (artifact.StartsWith("play-services-") || + artifact.StartsWith("com.google.android.gms.play-services-")) { + managedPlayServicesArtifacts.Add(filename); + } + } + managedArtifactFilenames.UnionWith(managedArtifacts.Values); + + // Gather all artifacts (AARs, JARs) that are not managed by the resolver. + var unmanagedArtifacts = new Dictionary>(); + var packagingExtensions = new HashSet(Dependency.Packaging); + // srcaar files are ignored by Unity so are not included in the build. + packagingExtensions.Remove(".srcaar"); + // List of paths to the legacy google-play-services.jar + var playServicesJars = new List(); + const string playServicesJar = "google-play-services.jar"; + + foreach (var packaging in packagingExtensions) { + foreach (var filename in + VersionHandlerImpl.SearchAssetDatabase( + String.Format("{0} t:Object", packaging), (string filtername) => { + // Ignore all assets that are managed by the plugin and anything + // that doesn't end with the packaging extension. + return !managedArtifactFilenames.Contains(filtername) && + Path.GetExtension(filtername).ToLower() == packaging; + })) { + if (Path.GetFileName(filename).ToLower() == playServicesJar) { + playServicesJars.Add(filename); + } else { + var versionlessFilename = getVersionlessArtifactFilename(filename); + List existing; + var unmanaged = unmanagedArtifacts.TryGetValue( + versionlessFilename, out existing) ? existing : new List(); + unmanaged.Add(filename); + unmanagedArtifacts[versionlessFilename] = unmanaged; + } + } + } + + // Check for conflicting Play Services versions. + // It's not possible to resolve this automatically as google-play-services.jar used to + // include all libraries so we don't know the set of components the developer requires. + if (managedPlayServicesArtifacts.Count > 0 && playServicesJars.Count > 0) { + PlayServicesResolver.Log( + String.Format( + "Legacy {0} found!\n\n" + + "Your application will not build in the current state.\n" + + "{0} library (found in the following locations):\n" + + "{1}\n" + + "\n" + + "{0} is incompatible with plugins that use newer versions of Google\n" + + "Play services (conflicting libraries in the following locations):\n" + + "{2}\n" + + "\n" + + "To resolve this issue find the plugin(s) that use\n" + + "{0} and either add newer versions of the required libraries or\n" + + "contact the plugin vendor to do so.\n\n", + playServicesJar, String.Join("\n", playServicesJars.ToArray()), + String.Join("\n", managedPlayServicesArtifacts.ToArray())), + level: LogLevel.Warning); + PlayServicesResolver.analytics.Report( + "/androidresolver/resolve/conflicts/duplicategoogleplayservices", + "Gradle Resolve: Duplicate Google Play Services Found"); + } + + // For each managed artifact aggregate the set of conflicting unmanaged artifacts. + var conflicts = new Dictionary>(); + foreach (var managed in managedArtifacts) { + List unmanagedFilenames; + if (unmanagedArtifacts.TryGetValue(managed.Key, out unmanagedFilenames)) { + // Found a conflict + List existingConflicts; + var unmanagedConflicts = conflicts.TryGetValue( + managed.Value, out existingConflicts) ? + existingConflicts : new List(); + unmanagedConflicts.AddRange(unmanagedFilenames); + conflicts[managed.Value] = unmanagedConflicts; + } + } + + // Warn about each conflicting version and attempt to resolve each conflict by removing + // older unmanaged versions. + Func getVersionFromFilename = (filename) => { + string basename = Path.GetFileNameWithoutExtension(Path.GetFileName(filename)); + return basename.Substring(getVersionlessArtifactFilename(basename).Length + 1); + }; + + // List of conflicts that haven't been removed. + var leftConflicts = new List(); + // Reports conflict count. + Action reportConflictCount = () => { + int numberOfConflicts = conflicts.Count; + if (numberOfConflicts > 0) { + PlayServicesResolver.analytics.Report( + "/androidresolver/resolve/conflicts/cleanup", + new KeyValuePair[] { + new KeyValuePair( + "numFound", numberOfConflicts.ToString()), + new KeyValuePair( + "numRemoved", + (numberOfConflicts - leftConflicts.Count).ToString()), + }, + "Gradle Resolve: Cleaned Up Conflicting Libraries"); + } + }; + + var conflictsEnumerator = conflicts.GetEnumerator(); + + // Move to the next conflicting package and prompt the user to delete a package. + Action promptToDeleteNextConflict = null; + + promptToDeleteNextConflict = () => { + bool conflictEnumerationComplete = false; + while (true) { + if (!conflictsEnumerator.MoveNext()) { + conflictEnumerationComplete = true; + break; + } + + var conflict = conflictsEnumerator.Current; + var currentVersion = getVersionFromFilename(conflict.Key); + var conflictingVersionsSet = new HashSet(); + foreach (var conflictFilename in conflict.Value) { + conflictingVersionsSet.Add(getVersionFromFilename(conflictFilename)); + } + var conflictingVersions = new List(conflictingVersionsSet); + conflictingVersions.Sort(Dependency.versionComparer); + + var warningMessage = String.Format( + "Found conflicting Android library {0}\n" + + "\n" + + "{1} (managed by the Android Resolver) conflicts with:\n" + + "{2}\n", + getVersionlessArtifactFilename(conflict.Key), + conflict.Key, String.Join("\n", conflict.Value.ToArray())); + + // If the conflicting versions are older than the current version we can + // possibly clean up the old versions automatically. + if (Dependency.versionComparer.Compare(conflictingVersions[0], + currentVersion) >= 0) { + DialogWindow.Display( + "Resolve Conflict?", + warningMessage + + "\n" + + "The conflicting libraries are older than the library managed by " + + "the Android Resolver. Would you like to remove the old libraries " + + "to resolve the conflict?", + DialogWindow.Option.Selected0, "Yes", "No", + complete: (selectedOption) => { + bool deleted = false; + if (selectedOption == DialogWindow.Option.Selected0) { + var deleteFailures = new List(); + foreach (var filename in conflict.Value) { + deleteFailures.AddRange( + FileUtils.DeleteExistingFileOrDirectory(filename)); + } + var deleteError = FileUtils.FormatError( + "Unable to delete old libraries", deleteFailures); + if (!String.IsNullOrEmpty(deleteError)) { + PlayServicesResolver.Log(deleteError, + level: LogLevel.Error); + } else { + deleted = true; + } + } + if (!deleted) leftConflicts.Add(warningMessage); + promptToDeleteNextConflict(); + }); + // Continue iteration when the dialog is complete. + break; + } + } + + if (conflictEnumerationComplete) { + reportConflictCount(); + foreach (var warningMessage in leftConflicts) { + PlayServicesResolver.Log( + warningMessage + + "\n" + + "Your application is unlikely to build in the current state.\n" + + "\n" + + "To resolve this problem you can try one of the following:\n" + + "* Updating the dependencies managed by the Android Resolver\n" + + " to remove references to old libraries. Be careful to not\n" + + " include conflicting versions of Google Play services.\n" + + "* Contacting the plugin vendor(s) with conflicting\n" + + " dependencies and asking them to update their plugin.\n", + level: LogLevel.Warning); + } + complete(); + } + }; + // Start prompting the user to delete conflicts. + promptToDeleteNextConflict(); + } + + /// + /// Perform the resolution and the exploding/cleanup as needed. + /// + /// Destination directory. + /// Whether to unconditionally close the resolution + /// window when complete. + /// Delegate called when resolution is complete. + public void DoResolution(string destinationDirectory, bool closeWindowOnCompletion, + System.Action resolutionComplete) { + // Run resolution on the main thread to serialize the operation as DoResolutionUnsafe + // is not thread safe. + RunOnMainThread.Run(() => { + DoResolutionUnsafe(destinationDirectory, closeWindowOnCompletion, + () => { + FindAndResolveConflicts(resolutionComplete); + }); + }); + } + + /// + /// Perform the resolution and the exploding/cleanup as needed. + /// This is *not* thread safe. + /// + /// Directory to store results of resolution. + /// Whether to unconditionally close the resolution + /// window when complete. + /// Action called when resolution completes. + private void DoResolutionUnsafe(string destinationDirectory, bool closeWindowOnCompletion, + System.Action resolutionComplete) + { + // Cache the setting as it can only be queried from the main thread. + var sdkPath = PlayServicesResolver.AndroidSdkRoot; + // If the Android SDK path isn't set or doesn't exist report an error. + if (String.IsNullOrEmpty(sdkPath) || !Directory.Exists(sdkPath)) { + PlayServicesResolver.analytics.Report("/resolve/gradle/failed/missingandroidsdk", + "Gradle Resolve: Failed Missing Android SDK"); + PlayServicesResolver.Log(String.Format( + "Android dependency resolution failed, your application will probably " + + "not run.\n\n" + + "Android SDK path must be set to a valid directory ({0})\n" + + "This must be configured in the 'Preference > External Tools > Android SDK'\n" + + "menu option.\n", String.IsNullOrEmpty(sdkPath) ? "{none}" : sdkPath), + level: LogLevel.Error); + resolutionComplete(); + return; + } + + System.Action resolve = () => { + PlayServicesResolver.Log("Performing Android Dependency Resolution", + LogLevel.Verbose); + GradleResolution(destinationDirectory, sdkPath, true, closeWindowOnCompletion, + (missingArtifacts) => { resolutionComplete(); }); + }; + + System.Action> reportOrInstallMissingArtifacts = + (List requiredDependencies) => { + // Set of packages that need to be installed. + var installPackages = new HashSet(); + // Retrieve the set of required packages and whether they're installed. + var requiredPackages = new Dictionary>(); + + if (requiredDependencies.Count == 0) { + resolutionComplete(); + return; + } + foreach (Dependency dependency in requiredDependencies) { + PlayServicesResolver.Log( + String.Format("Missing Android component {0} (Android SDK Packages: {1})", + dependency.Key, dependency.PackageIds != null ? + String.Join(",", dependency.PackageIds) : "(none)"), + level: LogLevel.Verbose); + if (dependency.PackageIds != null) { + foreach (string packageId in dependency.PackageIds) { + HashSet dependencySet; + if (!requiredPackages.TryGetValue(packageId, out dependencySet)) { + dependencySet = new HashSet(); + } + dependencySet.Add(dependency.Key); + requiredPackages[packageId] = dependencySet; + // Install / update the Android SDK package that hosts this dependency. + installPackages.Add(new AndroidSdkPackageNameVersion { + LegacyName = packageId + }); + } + } + } + + // If no packages need to be installed or Android SDK package installation is + // disabled. + if (installPackages.Count == 0 || !SettingsDialog.InstallAndroidPackages) { + // Report missing packages as warnings and try to resolve anyway. + foreach (var pkg in requiredPackages.Keys) { + var packageNameVersion = new AndroidSdkPackageNameVersion { + LegacyName = pkg }; + var depString = System.String.Join( + "\n", (new List(requiredPackages[pkg])).ToArray()); + if (installPackages.Contains(packageNameVersion)) { + PlayServicesResolver.Log( + String.Format( + "Android SDK package {0} is not installed or out of " + + "date.\n\n" + + "This is required by the following dependencies:\n" + + "{1}", pkg, depString), + level: LogLevel.Warning); + } + } + // At this point we've already tried resolving with Gradle. Therefore, + // Android SDK package installation is disabled or not required trying + // to resolve again only repeats the same operation we've already + // performed. So we just report report the missing artifacts as an error + // and abort. + var missingArtifacts = new List(); + foreach (var dep in requiredDependencies) missingArtifacts.Add(dep.Key); + LogMissingDependenciesError(missingArtifacts); + resolutionComplete(); + return; + } + InstallAndroidSdkPackagesAndResolve(sdkPath, installPackages, + requiredPackages, resolve); + }; + + GradleResolution(destinationDirectory, sdkPath, + !SettingsDialog.InstallAndroidPackages, closeWindowOnCompletion, + reportOrInstallMissingArtifacts); + } + + /// + /// Run the SDK manager to install the specified set of packages then attempt resolution + /// again. + /// + /// Path to the Android SDK. + /// Set of Android SDK packages to install. + /// The set dependencies for each Android SDK package. + /// This is used to report which dependencies can't be installed if Android SDK package + /// installation fails. + /// Action that performs resolution. + private void InstallAndroidSdkPackagesAndResolve( + string sdkPath, HashSet installPackages, + Dictionary> requiredPackages, System.Action resolve) { + // Find / upgrade the Android SDK manager. + AndroidSdkManager.Create( + sdkPath, + (IAndroidSdkManager sdkManager) => { + if (sdkManager == null) { + PlayServicesResolver.Log( + String.Format( + "Unable to find the Android SDK manager tool.\n\n" + + "The following Required Android packages cannot be installed:\n" + + "{0}\n" + + "\n" + + "{1}\n", + AndroidSdkPackageNameVersion.ListToString(installPackages), + String.IsNullOrEmpty(sdkPath) ? + PlayServicesSupport.AndroidSdkConfigurationError : ""), + level: LogLevel.Error); + return; + } + // Get the set of available and installed packages. + sdkManager.QueryPackages( + (AndroidSdkPackageCollection packages) => { + if (packages == null) return; + + // Filter the set of packages to install by what is available. + foreach (var packageName in requiredPackages.Keys) { + var pkg = new AndroidSdkPackageNameVersion { + LegacyName = packageName + }; + var depString = System.String.Join( + "\n", + (new List(requiredPackages[packageName])).ToArray()); + var availablePackage = + packages.GetMostRecentAvailablePackage(pkg.Name); + if (availablePackage == null || !availablePackage.Installed) { + PlayServicesResolver.Log( + String.Format( + "Android SDK package {0} ({1}) {2}\n\n" + + "This is required by the following dependencies:\n" + + "{3}\n", pkg.Name, pkg.LegacyName, + availablePackage != null ? + "not installed or out of date." : + "not available for installation.", + depString), + level: LogLevel.Warning); + if (availablePackage == null) { + installPackages.Remove(pkg); + } else if (!availablePackage.Installed) { + installPackages.Add(availablePackage); + } + } + } + if (installPackages.Count == 0) { + resolve(); + return; + } + // Start installation. + sdkManager.InstallPackages( + installPackages, (bool success) => { resolve(); }); + }); + }); + } + + /// + /// Convert an AAR filename to package name. + /// + /// Path of the AAR to convert. + /// AAR package name. + private string AarPathToPackageName(string aarPath) { + var aarFilename = Path.GetFileName(aarPath); + foreach (var extension in Dependency.Packaging) { + if (aarPath.EndsWith(extension)) { + return aarFilename.Substring(0, aarFilename.Length - extension.Length); + } + } + return aarFilename; + } + + /// + /// Get the target path for an exploded AAR. + /// + /// AAR file to explode. + /// Exploded AAR path. + private string DetermineExplodedAarPath(string aarPath) { + return Path.Combine(GooglePlayServices.SettingsDialog.PackageDir, + AarPathToPackageName(aarPath)); + } + + /// + /// Processes the aars. + /// + /// + /// + /// Each aar copied is inspected and determined if it should be + /// exploded into a directory or not. Unneeded exploded directories are + /// removed. + /// + /// + /// Exploding is needed if the version of Unity is old, or if the artifact + /// has been explicitly flagged for exploding. This allows the subsequent + /// processing of variables in the AndroidManifest.xml file which is not + /// supported by the current versions of the manifest merging process that + /// Unity uses. + /// + /// + /// The directory to process. + /// Set of files that were recently updated and should be + /// processed. + /// Called with the progress (0..1) and message that indicates + /// processing progress. + /// Executed when this process is complete. + private void ProcessAars(string dir, HashSet updatedFiles, + Action progressUpdate, Action complete) { + // Get set of AAR files and directories we're managing. + var uniqueAars = new HashSet(PlayServicesResolver.FindLabeledAssets()); + var aars = new Queue(uniqueAars); + + int numberOfAars = aars.Count; + if (numberOfAars == 0) { + complete(); + return; + } + + PlayServicesResolver.analytics.Report( + "/resolve/gradle/processaars", + new KeyValuePair[] { + new KeyValuePair("numPackages", numberOfAars.ToString()) + }, + "Gradle Resolve: Process AARs"); + var failures = new List(); + + // Processing can be slow so execute incrementally so we don't block the update thread. + RunOnMainThread.PollOnUpdateUntilComplete(() => { + int remainingAars = aars.Count; + bool allAarsProcessed = remainingAars == 0; + // Since the completion callback can trigger an update, remove this closure from + // the polling job list if complete. + if (allAarsProcessed) return true; + int processedAars = numberOfAars - remainingAars; + string aarPath = aars.Dequeue(); + remainingAars--; + allAarsProcessed = remainingAars == 0; + float progress = (float)processedAars / (float)numberOfAars; + try { + progressUpdate(progress, aarPath); + PlayServicesResolver.Log(String.Format("Processing {0}", aarPath), + level: LogLevel.Verbose); + if (updatedFiles.Contains(aarPath)) { + if (!ProcessAar(aarPath)) { + PlayServicesResolver.Log(String.Format( + "Failed to process {0}, your Android build will fail.\n" + + "See previous error messages for failure details.\n", + aarPath)); + failures.Add(aarPath); + } + } + } finally { + if (allAarsProcessed) { + progressUpdate(1.0f, "Library processing complete"); + if (failures.Count == 0) { + PlayServicesResolver.analytics.Report( + "/resolve/gradle/processaars/success", + new KeyValuePair[] { + new KeyValuePair("numPackages", + numberOfAars.ToString()) + }, + "Gradle Resolve: Process AARs Succeeded"); + } else { + PlayServicesResolver.analytics.Report( + "/resolve/gradle/processaars/failed", + new KeyValuePair[] { + new KeyValuePair("numPackages", + numberOfAars.ToString()), + new KeyValuePair("numPackagesFailed", + failures.Count.ToString()) + }, + "Gradle Resolve: Process AARs Failed"); + } + complete(); + } + } + return allAarsProcessed; + }); + } + + /// + /// Gets a value indicating whether this version of Unity supports aar files. + /// + /// true if supports aar files; otherwise, false. + internal static bool SupportsAarFiles + { + get + { + // Get the version number. + string majorVersion = Application.unityVersion.Split('.')[0]; + int ver; + if (!int.TryParse(majorVersion, out ver)) + { + ver = 4; + } + return ver >= 5; + } + } + + /// + /// Determines whether an AAR file should be processed. + /// + /// This is required for some AAR so that the plugin can perform variable expansion on + /// manifests and ABI stripping. + /// + /// Path of the unpacked AAR file to query. + /// true, if the AAR should be processed, false otherwise. + internal static bool ShouldProcess(string aarDirectory) { + // Unfortunately, as of Unity 5.5.0f3, Unity does not set the applicationId variable + // in the build.gradle it generates. This results in non-functional libraries that + // require the ${applicationId} variable to be expanded in their AndroidManifest.xml. + // To work around this when Gradle builds are enabled, explosion is enabled for all + // AARs that require variable expansion unless this behavior is explicitly disabled + // in the settings dialog. + if (!SettingsDialog.ExplodeAars) { + return false; + } + // If this version of Unity doesn't support AAR files, always explode. + if (!SupportsAarFiles) return true; + + const string manifestFilename = "AndroidManifest.xml"; + const string classesFilename = "classes.jar"; + string manifestPath = Path.Combine(aarDirectory, manifestFilename); + if (File.Exists(manifestPath)) { + string manifest = File.ReadAllText(manifestPath); + if (manifest.IndexOf("${applicationId}") >= 0) return true; + } + + // If the AAR is badly formatted (e.g does not contain classes.jar) + // explode it so that we can create classes.jar. + if (!File.Exists(Path.Combine(aarDirectory, classesFilename))) return true; + + // If the AAR contains more than one ABI and Unity's build is + // targeting a single ABI, explode it so that unused ABIs can be + // removed. + var availableAbis = AarDirectoryFindAbis(aarDirectory); + // Unity 2017's internal build system does not support AARs that contain + // native libraries so force explosion to pick up native libraries using + // Eclipse / Ant style projects. + if (availableAbis != null && + Google.VersionHandler.GetUnityVersionMajorMinor() >= 2017.0f) { + return true; + } + // NOTE: Unfortunately as of Unity 5.5 the internal Gradle build also blindly + // includes all ABIs from AARs included in the project so we need to explode the + // AARs and remove unused ABIs. + if (availableAbis != null) { + var abisToRemove = availableAbis.ToSet(); + abisToRemove.ExceptWith(AndroidAbis.Current.ToSet()); + if (abisToRemove.Count > 0) return true; + } + return false; + } + + /// + /// Create an AAR from the specified directory. + /// + /// AAR file to create. + /// Directory which contains the set of files to store + /// in the AAR. + /// true if successful, false otherwise. + internal static bool ArchiveAar(string aarFile, string inputDirectory) { + try { + string aarPath = Path.GetFullPath(aarFile); + CommandLine.Result result = CommandLine.Run( + JavaUtilities.JarBinaryPath, + String.Format("cvf{0} \"{1}\" -C \"{2}\" .", + aarFile.ToLower().EndsWith(".jar") ? "" : "M", aarPath, + inputDirectory)); + if (result.exitCode != 0) { + Debug.LogError(String.Format("Error archiving {0}\n" + + "Exit code: {1}\n" + + "{2}\n" + + "{3}\n", + aarPath, result.exitCode, result.stdout, + result.stderr)); + return false; + } + } catch (Exception e) { + Debug.LogError(e); + throw e; + } + return true; + } + + // Native library ABI subdirectories supported by Unity. + // Directories that contain native libraries within a Unity Android library project. + private static string[] NATIVE_LIBRARY_DIRECTORIES = new string[] { "libs", "jni" }; + + /// + /// Get the set of native library ABIs in an exploded AAR. + /// + /// Directory to search for ABIs. + /// Set of ABI directory names in the exploded AAR or null if none are + /// found. + internal static AndroidAbis AarDirectoryFindAbis(string aarDirectory) { + var foundAbis = new HashSet(); + foreach (var libDirectory in NATIVE_LIBRARY_DIRECTORIES) { + foreach (var abiDir in AndroidAbis.AllSupported) { + if (Directory.Exists(Path.Combine(aarDirectory, + Path.Combine(libDirectory, abiDir)))) { + foundAbis.Add(abiDir); + } + } + } + return foundAbis.Count > 0 ? new AndroidAbis(foundAbis) : null; + } + + /// + /// Process an AAR so that it can be included in a Unity build. + /// + /// Aar file to process. + /// true if successful, false otherwise. + internal static bool ProcessAar(string aarFile) { + PlayServicesResolver.Log(String.Format("ProcessAar {0}", aarFile), + level: LogLevel.Verbose); + + // If this isn't an aar ignore it. + if (!aarFile.ToLower().EndsWith(".aar")) return true; + + string aarDirName = Path.GetFileNameWithoutExtension(aarFile); + // Output directory for the contents of the AAR / JAR. + string outputDir = Path.Combine(Path.GetDirectoryName(aarFile), aarDirName); + string stagingDir = FileUtils.CreateTemporaryDirectory(); + if (stagingDir == null) { + PlayServicesResolver.Log(String.Format( + "Unable to create temporary directory to process AAR {0}", aarFile), + level: LogLevel.Error); + return false; + } + try { + string workingDir = Path.Combine(stagingDir, aarDirName); + var deleteError = FileUtils.FormatError( + String.Format("Failed to create working directory to process AAR {0}", + aarFile), FileUtils.DeleteExistingFileOrDirectory(workingDir)); + if (!String.IsNullOrEmpty(deleteError)) { + PlayServicesResolver.Log(deleteError, level: LogLevel.Error); + return false; + } + Directory.CreateDirectory(workingDir); + + if (!PlayServicesResolver.ExtractZip(aarFile, null, workingDir, false)) { + return false; + } + + bool process = ShouldProcess(workingDir); + // Determine whether an Ant style project should be generated for this artifact. + bool antProject = process && !PlayServicesResolver.GradleBuildEnabled; + + // If the AAR doesn't need to be processed or converted into an Ant project, + // we're done. + if (!(process || antProject)) return true; + + PlayServicesResolver.ReplaceVariablesInAndroidManifest( + Path.Combine(workingDir, "AndroidManifest.xml"), + PlayServicesResolver.GetAndroidApplicationId(), + new Dictionary()); + + string nativeLibsDir = null; + if (antProject) { + // Create the libs directory to store the classes.jar and non-Java shared + // libraries. + string libDir = Path.Combine(workingDir, "libs"); + nativeLibsDir = libDir; + Directory.CreateDirectory(libDir); + + // Move the classes.jar file to libs. + string classesFile = Path.Combine(workingDir, "classes.jar"); + string targetClassesFile = Path.Combine(libDir, Path.GetFileName(classesFile)); + if (File.Exists(targetClassesFile)) File.Delete(targetClassesFile); + if (File.Exists(classesFile)) { + FileUtils.MoveFile(classesFile, targetClassesFile); + } else { + // Some libraries publish AARs that are poorly formatted (e.g missing + // a classes.jar file). Firebase's license AARs at certain versions are + // examples of this. When Unity's internal build system detects an Ant + // project or AAR without a classes.jar, the build is aborted. This + // generates an empty classes.jar file to workaround the issue. + string emptyClassesDir = Path.Combine(stagingDir, "empty_classes_jar"); + Directory.CreateDirectory(emptyClassesDir); + if (!ArchiveAar(targetClassesFile, emptyClassesDir)) return false; + } + } + + // Copy non-Java shared libraries (.so) files from the "jni" directory into the + // lib directory so that Unity's legacy (Ant-like) build system includes them in the + // built APK. + string jniLibDir = Path.Combine(workingDir, "jni"); + nativeLibsDir = nativeLibsDir ?? jniLibDir; + if (Directory.Exists(jniLibDir)) { + var abisInArchive = AarDirectoryFindAbis(workingDir); + if (jniLibDir != nativeLibsDir) { + FileUtils.CopyDirectory(jniLibDir, nativeLibsDir); + deleteError = FileUtils.FormatError( + String.Format("Unable to delete JNI directory from AAR {0}", aarFile), + FileUtils.DeleteExistingFileOrDirectory(jniLibDir)); + if (!String.IsNullOrEmpty(deleteError)) { + PlayServicesResolver.Log(deleteError, level: LogLevel.Error); + return false; + } + } + if (abisInArchive != null) { + // Remove shared libraries for all ABIs that are not required for the + // selected ABIs. + var activeAbisSet = AndroidAbis.Current.ToSet(); + var abisInArchiveSet = abisInArchive.ToSet(); + var abisInArchiveToRemoveSet = new HashSet(abisInArchiveSet); + abisInArchiveToRemoveSet.ExceptWith(activeAbisSet); + + Func, string> setToString = (setToConvert) => { + return String.Join(", ", (new List(setToConvert)).ToArray()); + }; + PlayServicesResolver.Log( + String.Format( + "Target ABIs [{0}], ABIs [{1}] in {2}, will remove [{3}] ABIs", + setToString(activeAbisSet), + setToString(abisInArchiveSet), + aarFile, + setToString(abisInArchiveToRemoveSet)), + level: LogLevel.Verbose); + + foreach (var abiToRemove in abisInArchiveToRemoveSet) { + abisInArchiveSet.Remove(abiToRemove); + deleteError = FileUtils.FormatError( + String.Format("Unable to remove unused ABIs from {0}", aarFile), + FileUtils.DeleteExistingFileOrDirectory( + Path.Combine(nativeLibsDir, abiToRemove))); + if (!String.IsNullOrEmpty(deleteError)) { + PlayServicesResolver.Log(deleteError, LogLevel.Warning); + } + } + } + } + + if (antProject) { + // Create the project.properties file which indicates to Unity that this + // directory is a plugin. + string projectProperties = Path.Combine(workingDir, "project.properties"); + if (!File.Exists(projectProperties)) { + File.WriteAllLines(projectProperties, new [] { + "# Project target.", + "target=android-9", + "android.library=true" + }); + } + PlayServicesResolver.Log( + String.Format("Creating Ant project: Replacing {0} with {1}", aarFile, + outputDir), level: LogLevel.Verbose); + // Clean up the aar file. + deleteError = FileUtils.FormatError( + String.Format("Failed to clean up AAR file {0} after generating " + + "Ant project {1}", aarFile, outputDir), + FileUtils.DeleteExistingFileOrDirectory(Path.GetFullPath(aarFile))); + if (!String.IsNullOrEmpty(deleteError)) { + PlayServicesResolver.Log(deleteError, level: LogLevel.Error); + return false; + } + // Create the output directory. + FileUtils.MoveDirectory(workingDir, outputDir); + // Add a tracking label to the exploded files. + PlayServicesResolver.LabelAssets(new [] { outputDir }); + } else { + // Add a tracking label to the exploded files just in-case packaging fails. + PlayServicesResolver.Log(String.Format("Repacking {0} from {1}", + aarFile, workingDir), + level: LogLevel.Verbose); + // Create a new AAR file. + deleteError = FileUtils.FormatError( + String.Format("Failed to replace AAR file {0}", aarFile), + FileUtils.DeleteExistingFileOrDirectory(Path.GetFullPath(aarFile))); + if (!String.IsNullOrEmpty(deleteError)) { + PlayServicesResolver.Log(deleteError, level: LogLevel.Error); + return false; + } + if (!ArchiveAar(aarFile, workingDir)) return false; + PlayServicesResolver.LabelAssets(new [] { aarFile }); + } + } catch (Exception e) { + PlayServicesResolver.Log(String.Format("Failed to process AAR {0} ({1}", + aarFile, e), + level: LogLevel.Error); + } finally { + // Clean up the temporary directory. + var deleteError = FileUtils.FormatError( + String.Format("Failed to clean up temporary folder while processing {0}", + aarFile), FileUtils.DeleteExistingFileOrDirectory(stagingDir)); + if (!String.IsNullOrEmpty(deleteError)) { + PlayServicesResolver.Log(deleteError, level: LogLevel.Warning); + } + } + return true; + } + } +} diff --git a/source/AndroidResolver/src/GradleTemplateResolver.cs b/source/AndroidResolver/src/GradleTemplateResolver.cs new file mode 100644 index 00000000..ff9f75e5 --- /dev/null +++ b/source/AndroidResolver/src/GradleTemplateResolver.cs @@ -0,0 +1,800 @@ +// +// Copyright (C) 2019 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace GooglePlayServices { + using System; + using System.Collections.Generic; + using System.IO; + using System.Text.RegularExpressions; + + using Google; + using Google.JarResolver; + + using UnityEditor; + + /// + /// Resolver which simply injects dependencies into a gradle template file. + /// + internal class GradleTemplateResolver { + + /// + /// Filename of the Custom Gradle Properties Template file. + /// + public static string GradlePropertiesTemplateFilename = "gradleTemplate.properties"; + + /// + /// Path of the Custom Gradle Properties Template file in the Unity project. + /// Available only from Unity version 2019.3 onwards + /// + public static string GradlePropertiesTemplatePath = + Path.Combine(SettingsDialog.AndroidPluginsDir, GradlePropertiesTemplateFilename); + + /// + /// Filename of the Custom Gradle Settings Template file. + /// + public static string GradleSettingsTemplatePathFilename = "settingsTemplate.gradle"; + + /// + /// Path of the Custom Gradle Settings Template file. + /// + public static string GradleSettingsTemplatePath = + Path.Combine(SettingsDialog.AndroidPluginsDir, GradleSettingsTemplatePathFilename); + + public static string UnityGradleTemplatesDir { + get { + if (unityGradleTemplatesDir == null) { + var engineDir = PlayServicesResolver.AndroidPlaybackEngineDirectory; + if (String.IsNullOrEmpty(engineDir)) return null; + var gradleTemplateDir = + Path.Combine(Path.Combine(engineDir, "Tools"), "GradleTemplates"); + unityGradleTemplatesDir = gradleTemplateDir; + } + return unityGradleTemplatesDir; + } + } + private static string unityGradleTemplatesDir = null; + + public static string UnityGradleSettingsTemplatePath { + get { + return Path.Combine( + UnityGradleTemplatesDir, + GradleSettingsTemplatePathFilename); + } + } + + /// + /// Line that indicates the start of the injected properties block in properties template. + /// + private const string PropertiesStartLine = "# Android Resolver Properties Start"; + + /// + /// Line that indicates the end of the injected properties block in properties template. + /// + private const string PropertiesEndLine = "# Android Resolver Properties End"; + + /// + /// Filename of the Custom Main Gradle Template file. + /// + public static string GradleTemplateFilename = "mainTemplate.gradle"; + + /// + /// Path of the Gradle template file. + /// + public static string GradleTemplatePath = + Path.Combine(SettingsDialog.AndroidPluginsDir, GradleTemplateFilename); + + /// + /// Line that indicates the start of the injected repos block in the template. + /// + private const string ReposStartLine = "// Android Resolver Repos Start"; + + /// + /// Line that indicates the end of the injected repos block in the template. + /// + private const string ReposEndLine = "// Android Resolver Repos End"; + + /// + /// Line that indicates where to initially inject repos in the default template. + /// + private const string ReposInjectionLine = + @".*apply plugin: 'com\.android\.(application|library)'.*"; + + /// + /// Line that indicates where to initially inject repos in the settings template. + /// + private const string ReposInjectionLineInGradleSettings = + @".*flatDir {"; + + /// + /// Token that indicates where gradle properties should initially be injected. + /// If this isn't present in the properties template, properties will not be + /// injected or they'll be removed. + /// + private const string PropertiesInjectionLine = @"ADDITIONAL_PROPERTIES"; + + /// + /// Line that indicates the start of the injected dependencies block in the template. + /// + private const string DependenciesStartLine = "// Android Resolver Dependencies Start"; + + /// + /// Line that indicates the end of the injected dependencies block in the template. + /// + private const string DependenciesEndLine = "// Android Resolver Dependencies End"; + + /// + /// Token that indicates where dependencies should initially be injected. + /// If this isn't present in the template dependencies will not be injected or they'll + /// be removed. + /// + private const string DependenciesToken = @".*\*\*DEPS\*\*.*"; + + /// + /// Line that indicates the start of the injected exclusions block in the template. + /// + private const string PackagingOptionsStartLine = "// Android Resolver Exclusions Start"; + + /// + /// Line that indicates the end of the injected exclusions block in the template. + /// + private const string PackagingOptionsEndLine = "// Android Resolver Exclusions End"; + + /// + /// Token that indicates where exclusions should be injected. + /// + private const string PackagingOptionsToken = @"android +{"; + + /// + /// Whether current of Unity has changed the place to specify Maven repos from + /// `mainTemplate.gradle` to `settingsTemplate.gradle`. + /// + public static bool UnityChangeMavenRepoInSettingsTemplate { + get { + // Check against the Android Gradle plugin version being used, since that can + // change between Unity minor versions. + return (new Dependency.VersionComparer()).Compare( + "7.0", PlayServicesResolver.AndroidGradlePluginVersion) >= 0; + } + } + + /// + /// Modifies the given path such that the m2repository is placed into the + /// LocalMavenRepoDir/PrefixDirectory/m2repository/... + /// + /// The original path to modify. + /// A modified path if m2repository is found, the same path otherwise. + private static string ReplaceLocalFolderBasedOnM2repo(string path) { + string regexPattern = @"^(.*[/\\])([^/\\]+[/\\]m2repository.*)$"; + Match match = Regex.Match(path, regexPattern); + if (match.Success) { + path = Path.Combine(GooglePlayServices.SettingsDialog.LocalMavenRepoDir, + match.Groups[2].Value); + } + return path; + } + + /// + /// Copy srcaar files to aar files that are excluded from Unity's build process. + /// + /// Dependencies to inject. + /// true if successful, false otherwise. + private static bool CopySrcAars(ICollection dependencies) { + bool succeeded = true; + var aarFiles = new List>(); + // Copy each .srcaar file to .aar while configuring the plugin importer to ignore the + // file. + foreach (var aar in LocalMavenRepository.FindAarsInLocalRepos(dependencies)) { + // Only need to copy for .srcaar + if (Path.GetExtension(aar).CompareTo(".srcaar") != 0) { + continue; + } + + var aarPath = aar; + if (FileUtils.IsUnderPackageDirectory(aar)) { + // Physical paths work better for copy operations than + // logical Unity paths. + var physicalPackagePath = FileUtils.GetPackageDirectory(aar, + FileUtils.PackageDirectoryType.PhysicalPath); + aarPath = FileUtils.ReplaceBaseAssetsOrPackagesFolder( + aar, physicalPackagePath); + } + var dir = FileUtils.ReplaceBaseAssetsOrPackagesFolder( + Path.GetDirectoryName(aar), + GooglePlayServices.SettingsDialog.LocalMavenRepoDir); + + if (!dir.StartsWith(GooglePlayServices.SettingsDialog.LocalMavenRepoDir)) { + // The directory replace logic failed, likely because the original aar + // is not located under the Assets or Packages folders. + // Try to come up with a sensible destination folder by searching for + // an m2repository within the path, and using that. + dir = ReplaceLocalFolderBasedOnM2repo(Path.GetDirectoryName(aar)); + } + + var filename = Path.GetFileNameWithoutExtension(aarPath); + var targetFilename = Path.Combine(dir, filename + ".aar"); + + // Avoid situations where we can have a mix of file path + // separators based on platform. + aarPath = FileUtils.NormalizePathSeparators(aarPath); + targetFilename = FileUtils.NormalizePathSeparators( + targetFilename); + + bool configuredAar = File.Exists(targetFilename); + if (!configuredAar) { + var error = PlayServicesResolver.CopyAssetAndLabel( + aarPath, targetFilename); + if (String.IsNullOrEmpty(error)) { + try { + PluginImporter importer = (PluginImporter)AssetImporter.GetAtPath( + targetFilename); + importer.SetCompatibleWithAnyPlatform(false); + importer.SetCompatibleWithPlatform(BuildTarget.Android, false); + configuredAar = true; + } catch (Exception ex) { + PlayServicesResolver.Log(String.Format( + "Failed to disable {0} from being included by Unity's " + + "internal build. {0} has been deleted and will not be " + + "included in Gradle builds. ({1})", aar, ex), + level: LogLevel.Error); + } + } else { + PlayServicesResolver.Log(String.Format( + "Unable to copy {0} to {1}. {1} will not be included in Gradle " + + "builds. Reason: {2}", aarPath, targetFilename, error), + level: LogLevel.Error); + } + } + if (configuredAar) { + aarFiles.Add(new KeyValuePair(aarPath, targetFilename)); + // Some versions of Unity do not mark the asset database as dirty when + // plugin importer settings change so reimport the asset to synchronize + // the state. + AssetDatabase.ImportAsset(targetFilename, ImportAssetOptions.ForceUpdate); + } else { + if (File.Exists(targetFilename)) { + AssetDatabase.DeleteAsset(targetFilename); + } + succeeded = false; + } + } + foreach (var keyValue in aarFiles) { + succeeded &= LocalMavenRepository.PatchPomFile(keyValue.Value, keyValue.Key); + } + return succeeded; + } + + /// + /// Finds an area in a set of lines to inject a block of text. + /// + private class TextFileLineInjector { + // Token which, if found within a line, indicates where to inject the block of text if + // the start / end block isn't found + private Regex injectionToken; + // Line that indicates the start of the block to replace. + private string startBlockLine; + // Line that indicates the end of the block to replace. + private string endBlockLine; + // Lines to inject. + private List replacementLines; + // Shorthand name of the replacement block. + private string replacementName; + // Description of the file being modified. + private string fileDescription; + // Whether replacementLines has been injected. + private bool injected = false; + // Whether the injector is tracking a line between startBlockLine and endBlockLine. + private bool inBlock = false; + + /// + /// Construct the injector. + /// + /// Regular expression, if found within a line, + /// indicates where to inject the block of text if the start / end block isn't + /// found. + /// Line which indicates the start of the block to + /// replace. + /// Line which indicates the end of the block to replace. + /// + /// Lines to inject. + /// Shorthand name of the replacement block. + /// Description of the file being modified. + public TextFileLineInjector(string injectionToken, + string startBlockLine, + string endBlockLine, + ICollection replacementLines, + string replacementName, + string fileDescription) { + this.injectionToken = new Regex(injectionToken); + this.startBlockLine = startBlockLine; + this.endBlockLine = endBlockLine; + this.replacementLines = new List(); + if (replacementLines.Count > 0) { + this.replacementLines.Add(startBlockLine); + this.replacementLines.AddRange(replacementLines); + this.replacementLines.Add(endBlockLine); + } + this.replacementName = replacementName; + this.fileDescription = fileDescription; + } + + /// + /// Process a line returning the set of lines to emit for this line. + /// + /// Line to process. + /// Whether lines were injected. + /// List of lines to emit for the specified line. + public List ProcessLine(string line, out bool injectionApplied) { + var trimmedLine = line.Trim(); + var outputLines = new List { line }; + bool injectBlock = false; + injectionApplied = false; + if (injected) { + return outputLines; + } + if (!inBlock) { + if (trimmedLine.StartsWith(startBlockLine)) { + inBlock = true; + outputLines.Clear(); + } else if (injectionToken.IsMatch(trimmedLine)) { + injectBlock = true; + } + } else { + outputLines.Clear(); + if (trimmedLine.StartsWith(endBlockLine)) { + inBlock = false; + injectBlock = true; + } + } + if (injectBlock) { + injected = true; + injectionApplied = true; + if (replacementLines.Count > 0) { + PlayServicesResolver.Log(String.Format("Adding {0} to {1}", + replacementName, fileDescription), + level: LogLevel.Verbose); + outputLines.InsertRange(0, replacementLines); + } + } + return outputLines; + } + } + + /// + /// Patch file contents by injecting custom data. + /// + /// Path to file to modify + /// Used in logs for describing the file + /// Name used in analytics logs + /// Token used in forming analytics path + /// Array of text injectors + /// used in analytics reporting + /// true if successful, false otherwise. + private static bool PatchFile(string filePath, + string fileDescription, + string analyticsReportName, + string analyticsReportUrlToken, + TextFileLineInjector[] injectors, + ICollection> + resolutionMeasurementParameters) { + IEnumerable lines; + try { + lines = File.ReadAllLines(filePath); + } catch (Exception ex) { + PlayServicesResolver.analytics.Report( + "/resolve/" + analyticsReportUrlToken + "/failed/templateunreadable", + analyticsReportName + " Resolve: Failed Template Unreadable"); + PlayServicesResolver.Log( + String.Format("Unable to patch {0} ({1})", fileDescription, ex.ToString()), + level: LogLevel.Error); + return false; + } + + // Lines that will be written to the output file. + var outputLines = new List(); + foreach (var line in lines) { + var currentOutputLines = new List(); + foreach (var injector in injectors) { + bool injectionApplied = false; + currentOutputLines = injector.ProcessLine(line, out injectionApplied); + if (injectionApplied || currentOutputLines.Count == 0) break; + } + outputLines.AddRange(currentOutputLines); + } + + var inputText = String.Join("\n", (new List(lines)).ToArray()) + "\n"; + var outputText = String.Join("\n", outputLines.ToArray()) + "\n"; + + if (inputText == outputText) { + PlayServicesResolver.Log(String.Format("No changes to {0}", fileDescription), + level: LogLevel.Verbose); + return true; + } + return WriteToFile(filePath, fileDescription, outputText, + analyticsReportName, analyticsReportUrlToken, + resolutionMeasurementParameters); + } + + /// + /// Write new contents to a file on disk + /// + /// Path to file to modify + /// Used in logs for describing the file + /// Updated contents to write to the file + /// Name used in analytics logs + /// Token used in forming analytics path + /// used in analytics reporting + /// true if successful, false otherwise. + private static bool WriteToFile(string filePath, + string fileDescription, + string outputText, + string analyticsReportName, + string analyticsReportUrlToken, + ICollection> + resolutionMeasurementParameters) { + if (!FileUtils.CheckoutFile(filePath, PlayServicesResolver.logger)) { + PlayServicesResolver.Log( + String.Format("Failed to checkout '{0}', unable to patch the file.", + filePath), level: LogLevel.Error); + PlayServicesResolver.analytics.Report( + "/resolve/" + analyticsReportUrlToken + "/failed/checkout", + analyticsReportName + " Resolve: Failed to checkout"); + return false; + } + PlayServicesResolver.Log( + String.Format("Writing updated {0}", fileDescription), + level: LogLevel.Verbose); + try { + File.WriteAllText(filePath, outputText); + PlayServicesResolver.analytics.Report( + "/resolve/"+analyticsReportUrlToken+"/success", + resolutionMeasurementParameters, + analyticsReportName + " Resolve Success"); + } catch (Exception ex) { + PlayServicesResolver.analytics.Report( + "/resolve/"+analyticsReportUrlToken+"/failed/write", + analyticsReportName + " Resolve: Failed to write"); + PlayServicesResolver.Log( + String.Format("Unable to patch {0} ({1})", fileDescription, + ex.ToString()), level: LogLevel.Error); + return false; + } + return true; + } + + /// + /// Inject properties in the gradle properties template file. + /// Because of a change in structure of android projects built with + /// Unity 2019.3 and above, the correct way to enable jetifier and + /// Android X is by updating the gradle properties template. + /// + /// true if successful, false otherwise. + public static bool InjectProperties(){ + var resolutionMeasurementParameters = + PlayServicesResolver.GetResolutionMeasurementParameters(null); + PlayServicesResolver.analytics.Report( + "/resolve/gradleproperties", resolutionMeasurementParameters, + "Gradle Properties Resolve"); + var propertiesLines = new List(); + // Lines to add Custom Gradle properties template to enable + // jetifier and androidx + propertiesLines.AddRange(new [] { + "android.useAndroidX=true", + "android.enableJetifier=true", + }); + var propertiesFileDescription = String.Format( + "gradle properties template" + GradlePropertiesTemplatePath); + TextFileLineInjector[] propertiesInjectors = new [] { + new TextFileLineInjector(PropertiesInjectionLine, + PropertiesStartLine, PropertiesEndLine, + propertiesLines, + "Properties", + propertiesFileDescription) + }; + if (!PatchFile(GradlePropertiesTemplatePath, propertiesFileDescription, + "Gradle Properties", "gradleproperties", + propertiesInjectors, + resolutionMeasurementParameters)) { + PlayServicesResolver.Log( + String.Format("Unable to patch " + propertiesFileDescription), + level: LogLevel.Error); + return false; + } + return true; + } + + /// + /// Inject / update additional Maven repository urls specified from `Dependencies.xml` in + /// the Gradle settings template file. + /// + /// Dependencies to inject. + /// true if successful, false otherwise. + public static bool InjectDependencies(ICollection dependencies) { + var resolutionMeasurementParameters = + PlayServicesResolver.GetResolutionMeasurementParameters(null); + if (dependencies.Count > 0) { + PlayServicesResolver.analytics.Report( + "/resolve/gradletemplate", resolutionMeasurementParameters, + "Gradle Template Resolve"); + } + + var fileDescription = String.Format("gradle template {0}", GradleTemplatePath); + PlayServicesResolver.Log(String.Format("Reading {0}", fileDescription), + level: LogLevel.Verbose); + IEnumerable lines; + try { + lines = File.ReadAllLines(GradleTemplatePath); + } catch (Exception ex) { + PlayServicesResolver.analytics.Report( + "/resolve/gradletemplate/failed/templateunreadable", + "Gradle Template Resolve: Failed Template Unreadable"); + PlayServicesResolver.Log( + String.Format("Unable to patch {0} ({1})", fileDescription, ex.ToString()), + level: LogLevel.Error); + return false; + } + + PlayServicesResolver.Log(String.Format("Searching for {0} in {1}", DependenciesToken, + fileDescription), + level: LogLevel.Verbose); + // Determine whether dependencies should be injected. + var dependenciesToken = new Regex(DependenciesToken); + bool containsDeps = false; + foreach (var line in lines) { + if (dependenciesToken.IsMatch(line)) { + containsDeps = true; + break; + } + } + + // If a dependencies token isn't present report a warning and abort. + if (!containsDeps) { + PlayServicesResolver.analytics.Report( + "/resolve/gradletemplate/failed/noinjectionpoint", + "Gradle Template Resolve: Failed No Injection Point"); + PlayServicesResolver.Log( + String.Format("No {0} token found in {1}, Android Resolver libraries will " + + "not be added to the file.", DependenciesToken, fileDescription), + level: LogLevel.Warning); + return true; + } + + // Copy all srcaar files in the project to aar filenames so that they'll be included in + // the Gradle build. + if (!CopySrcAars(dependencies)) { + PlayServicesResolver.analytics.Report( + "/resolve/gradletemplate/failed/srcaarcopy", + "Gradle Template Resolve: Failed srcaar I/O"); + return false; + } + + var repoLines = new List(); + // Optionally enable the jetifier. + if (SettingsDialog.UseJetifier && dependencies.Count > 0) { + // For Unity versions lower than 2019.3 add jetifier and AndroidX + // properties to custom main gradle template + if (VersionHandler.GetUnityVersionMajorMinor() < 2019.3f) { + repoLines.AddRange(new [] { + "([rootProject] + (rootProject.subprojects as List)).each {", + " ext {", + " it.setProperty(\"android.useAndroidX\", true)", + " it.setProperty(\"android.enableJetifier\", true)", + " }", + "}" + }); + } + } + if (!UnityChangeMavenRepoInSettingsTemplate) { + repoLines.AddRange(PlayServicesResolver.GradleMavenReposLines(dependencies)); + } + + TextFileLineInjector[] injectors = new [] { + new TextFileLineInjector(ReposInjectionLine, ReposStartLine, ReposEndLine, + repoLines, "Repos", fileDescription), + new TextFileLineInjector(DependenciesToken, DependenciesStartLine, + DependenciesEndLine, + PlayServicesResolver.GradleDependenciesLines( + dependencies, includeDependenciesBlock: false), + "Dependencies", fileDescription), + new TextFileLineInjector(PackagingOptionsToken, PackagingOptionsStartLine, + PackagingOptionsEndLine, + PlayServicesResolver.PackagingOptionsLines(dependencies), + "Packaging Options", fileDescription), + }; + return PatchFile(GradleTemplatePath, fileDescription, + "Gradle Template", "gradletemplate", + injectors, resolutionMeasurementParameters); + } + + /// + /// Inject / update dependencies in the gradle template file. + /// + /// true if successful, false otherwise. + public static bool InjectSettings(ICollection dependencies, out string lastError) { + if (!UnityChangeMavenRepoInSettingsTemplate || + !PlayServicesResolver.GradleTemplateEnabled) { + // Early out since there is no need to patch settings template. + lastError = ""; + return true; + } + + if (!EnsureGradleTemplateEnabled(GradleSettingsTemplatePathFilename)) { + lastError = String.Format( + "Failed to auto-generate '{0}'. This is required to specify " + + "additional Maven repos from Unity 2022.2. " + + "Please manually generate '{2}' through one " + + "of the following methods:\n" + + "* For Unity 2022.2.10+, enable 'Custom Gradle Settings Template' " + + "found under 'Player Settings > Settings for Android -> Publishing " + + "Settings' menu. \n" + + "* Manually copy '{1}' to '{2}'\n" + + "If you like to patch this yourself, simply disable 'Copy and patch " + + "settingsTemplate.gradle' in Android Resolver settings.", + GradleSettingsTemplatePathFilename, + UnityGradleSettingsTemplatePath, + GradleSettingsTemplatePath); + return false; + } + + // ReposInjectionLineInGradleSettings + + var resolutionMeasurementParameters = + PlayServicesResolver.GetResolutionMeasurementParameters(null); + PlayServicesResolver.analytics.Report( + "/resolve/gradlesettings", resolutionMeasurementParameters, + "Gradle Settings Template Resolve"); + + var settingsFileDescription = String.Format( + "gradle settings template " + GradleSettingsTemplatePath); + + TextFileLineInjector[] settingsInjectors = new [] { + new TextFileLineInjector(ReposInjectionLineInGradleSettings, + ReposStartLine, ReposEndLine, + GradleMavenReposLinesFromDependencies( + dependencies: dependencies, + addMavenGoogle: false, + addMavenCentral: false, + addMavenLocal: true), + "Repo", + settingsFileDescription) + }; + if (!PatchFile(GradleSettingsTemplatePath, settingsFileDescription, + "Gradle Settings", "gradlesettings", + settingsInjectors, + resolutionMeasurementParameters)) { + lastError = String.Format("Unable to patch " + settingsFileDescription); + return false; + } + + lastError = ""; + return true; + } + + /// + /// Get the included dependency repos as lines that can be included in a Gradle file. + /// + /// Lines that can be included in a gradle file. + internal static IList GradleMavenReposLinesFromDependencies( + ICollection dependencies, + bool addMavenGoogle, + bool addMavenCentral, + bool addMavenLocal) { + var lines = new List(); + if (dependencies.Count > 0) { + var exportEnabled = PlayServicesResolver.GradleProjectExportEnabled; + var useFullPath = ( + exportEnabled && + SettingsDialog.UseFullCustomMavenRepoPathWhenExport ) || ( + !exportEnabled && + SettingsDialog.UseFullCustomMavenRepoPathWhenNotExport); + + var projectPath = FileUtils.PosixPathSeparators(Path.GetFullPath(".")); + var projectFileUri = GradleResolver.RepoPathToUri(projectPath); + // projectPath will point to the Unity project root directory as Unity will + // generate the root Gradle project in "Temp/gradleOut" when *not* exporting a + // gradle project. + if (!useFullPath) { + lines.Add(String.Format( + " def unityProjectPath = $/{0}**DIR_UNITYPROJECT**/$" + + ".replace(\"\\\\\", \"/\")", GradleWrapper.FILE_SCHEME)); + } + if(addMavenGoogle) { + lines.Add(" maven {"); + lines.Add(" url \"https://maven.google.com/""); + lines.Add(" }"); + } + // Consolidate repos url from Packages folders like + // "Packages/com.company.pkg1/Path/To/m2repository" and + // "Packages/com.company.pkg2/Path/To/m2repository" + Dictionary< string, List > repoUriToSources = + new Dictionary>(); + foreach (var repoAndSources in PlayServicesResolver.GetRepos(dependencies: dependencies)) { + string repoUri; + if (repoAndSources.Key.StartsWith(projectFileUri)) { + var relativePath = repoAndSources.Key.Substring(projectFileUri.Length + 1); + // Convert "Assets", "Packages/packageid", or + // "Library/PackageCache/packageid@version" prefix (@version optional) to local + // maven repo path. Note that local maven repo path only exists if the original + // repo path contains .srcaar. + var repoPath = FileUtils.ReplaceBaseAssetsOrPackagesFolder( + relativePath, GooglePlayServices.SettingsDialog.LocalMavenRepoDir); + // We also want to just convert any prefixes before a directory/m2repository, since + // they are copied to the LocalMavenRepoDir as well. + repoPath = ReplaceLocalFolderBasedOnM2repo(repoPath); + if (!Directory.Exists(repoPath)) { + repoPath = relativePath; + } + repoPath = FileUtils.PosixPathSeparators(repoPath); + + if (useFullPath) { + // build.gradle expects file:/// URI so file separator will be "/" in anycase + // and we must NOT use Path.Combine here because it will use "\" for win platforms + repoUri = String.Format("\"{0}/{1}\"", projectFileUri, repoPath); + } else { + repoUri = String.Format("(unityProjectPath + \"/{0}\")", repoPath); + } + } else { + repoUri = String.Format("\"{0}\"", repoAndSources.Key); + } + List sources; + if (!repoUriToSources.TryGetValue(repoUri, out sources)) { + sources = new List(); + repoUriToSources[repoUri] = sources; + } + sources.Add(repoAndSources.Value); + } + foreach(var kv in repoUriToSources) { + lines.Add(" maven {"); + lines.Add(String.Format(" url {0} // {1}", kv.Key, + String.Join(", ", kv.Value.ToArray()))); + lines.Add(" }"); + } + if (addMavenLocal) { + lines.Add(" mavenLocal()"); + } + if (addMavenCentral) { + lines.Add(" mavenCentral()"); + } + } + return lines; + } + + public static bool EnsureGradleTemplateEnabled(string templateName) { + string templatePath = Path.Combine(SettingsDialog.AndroidPluginsDir, templateName); + if (File.Exists(templatePath)) { + return true; + } + + string templateSourcePath = Path.Combine(UnityGradleTemplatesDir, templateName); + + try { + File.Copy(templateSourcePath, templatePath); + } catch (Exception e) { + PlayServicesResolver.Log(String.Format( + "Unable to copy '{0}' from Unity engine folder '{1}' to this project " + + "folder '{2}'. \n {3}", + templateName, + UnityGradleTemplatesDir, + SettingsDialog.AndroidPluginsDir, + e.ToString()), LogLevel.Error); + return false; + } + PlayServicesResolver.Log(String.Format( + "Copied '{0}' from Unity engine folder to this project '{1}'", + templateName, SettingsDialog.AndroidPluginsDir)); + return true; + } + } +} diff --git a/source/AndroidResolver/src/GradleWrapper.cs b/source/AndroidResolver/src/GradleWrapper.cs new file mode 100644 index 00000000..4c667d8d --- /dev/null +++ b/source/AndroidResolver/src/GradleWrapper.cs @@ -0,0 +1,259 @@ +// +// Copyright (C) 2019 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Google { + using System; + using System.Collections.Generic; + using System.IO; + using System.Reflection; + using GooglePlayServices; + + /// + /// Provides methods to unpack and execute the embedded Gradle wrapper. + /// + internal class GradleWrapper { + + /// + /// Embedded resource zip file that contains the Gradle wrapper. + /// + private string archiveResource; + + /// + /// Assembly that contains the archiveResource. + /// + private Assembly resourceAssembly; + + /// + /// Directory containing the Gradle wrapper. + /// + private string buildDirectory; + + /// + /// Create an instance to manage the Gradle wrapper. + /// + /// Assembly that contains archiveResource. + /// Embedded zip archive resource that contains the Gradle + /// wrapper. + /// Directory to extract the Gradle wrapper to and executed it + /// from. + public GradleWrapper(Assembly resourceAssembly, string archiveResource, + string buildDirectory) { + this.resourceAssembly = resourceAssembly; + this.archiveResource = archiveResource; + this.buildDirectory = buildDirectory; + } + + /// + /// Get the location of the archive on the local filesystem containing the Gradle wrapper. + /// + private string Archive { + get { + return Path.Combine(BuildDirectory, archiveResource); + } + } + + /// + /// Get the directory containing the Gradle wrapper. + /// + public string BuildDirectory { get { return buildDirectory; } } + + /// + /// Returns the Gradle wrapper executable path for the current platform. + /// + public string Executable { + get { + return Path.GetFullPath( + Path.Combine(BuildDirectory, + UnityEngine.RuntimePlatform.WindowsEditor == + UnityEngine.Application.platform ? "gradlew.bat" : "gradlew")); + } + } + + /// + /// Gradle wrapper files to extract from the ARCHIVE_RESOURCE. + /// + private static string[] archivedFiles = new [] { + "gradle/wrapper/gradle-wrapper.jar", + "gradle/wrapper/gradle-wrapper.properties", + "gradlew", + "gradlew.bat" + }; + + /// + /// Extract the gradle wrapper and prepare it for use. + /// + /// Logger to report errors to. + public bool Extract(Logger logger) { + if (!(EmbeddedResource.ExtractResources( + resourceAssembly, + new KeyValuePair[] { + new KeyValuePair(archiveResource, Archive) + }, logger) && + PlayServicesResolver.ExtractZip(Archive, archivedFiles, BuildDirectory, true))) { + logger.Log(String.Format("Failed to extract Gradle wrapper resource {0}", + Archive), level: LogLevel.Error); + return false; + } + var executable = Executable; + // Files extracted from the zip file don't have the executable bit set on some + // platforms, so set it here. + // Unfortunately, File.GetAccessControl() isn't implemented, so we'll use + // chmod (OSX / Linux) and on Windows extracted files are executable by default + // so we do nothing. + if (UnityEngine.RuntimePlatform.WindowsEditor != UnityEngine.Application.platform) { + var result = CommandLine.Run("chmod", String.Format("ug+x \"{0}\"", executable)); + if (result.exitCode != 0) { + logger.Log(String.Format("Failed to make \"{0}\" executable.\n\n{1}", + executable, result.message), + level: LogLevel.Error); + return false; + } + } + return true; + } + + /// + /// Prepare Gradle for execution and call a closure with the command line parameters to + /// execute the wrapper. + /// + /// Whether to use the Gradle daemon. + /// Path to the Gradle build script to use. + /// Project properties to use when running the script. + /// + /// Other arguments to pass to Gradle. + /// Logger to report errors to. + /// Closure which takes the tool path to execute + /// (gradle wrapper) and a string of arguments returning true if successful, + /// false otherwise. + /// true if successful, false otherwise. + public bool Run(bool useGradleDaemon, string buildScript, + Dictionary projectProperties, + string arguments, Logger logger, + Func executeCommand) { + var allArguments = new List() { + useGradleDaemon ? "--daemon" : "--no-daemon", + String.Format("-b \"{0}\"", Path.GetFullPath(buildScript)), + }; + if (!String.IsNullOrEmpty(arguments)) allArguments.Add(arguments); + foreach (var kv in projectProperties) { + allArguments.Add(String.Format("\"-P{0}={1}\"", kv.Key, kv.Value)); + } + var argumentsString = String.Join(" ", allArguments.ToArray()); + + // Generate gradle.properties to set properties in the script rather than using + // the command line. + // Some users of Windows 10 systems have had issues running the Gradle resolver + // which is suspected to be caused by command line argument parsing or truncation. + // Using both gradle.properties and properties specified via command line arguments + // works fine. + try { + File.WriteAllText(Path.Combine(BuildDirectory, "gradle.properties"), + GradleWrapper.GenerateGradleProperties(projectProperties)); + } catch (IOException error) { + logger.Log(String.Format("Unable to configure Gradle for execution " + + "({0} {1})\n\n{2}", + Executable, argumentsString, error), + level: LogLevel.Error); + return false; + } + logger.Log(String.Format("Running Gradle...\n\n{0} {1}", Executable, argumentsString), + level: LogLevel.Verbose); + return executeCommand(Executable, argumentsString); + } + + // Characters that are parsed by Gradle / Java in property values. + // These characters need to be escaped to be correctly interpreted in a property value. + private static string[] GradlePropertySpecialCharacters = new string[] { + " ", "\\", "#", "!", "=", ":" + }; + + /// + /// Escape all special characters in a gradle property value. + /// + /// Value to escape. + /// Function which generates an escaped character. By default + /// this adds "\\" to each escaped character. + /// Characters to exclude from the escaping set. + /// Escaped value. + public static string EscapeGradlePropertyValue( + string value, Func escapeFunc = null, + HashSet charactersToExclude = null) { + if (escapeFunc == null) { + escapeFunc = (characterToEscape) => { return "\\" + characterToEscape; }; + } + foreach (var characterToEscape in GradlePropertySpecialCharacters) { + if (charactersToExclude == null || + !(charactersToExclude.Contains(characterToEscape))) { + value = value.Replace(characterToEscape, escapeFunc(characterToEscape)); + } + } + return value; + } + + /// + /// Generates a Gradle (Java) properties string from a dictionary of key value pairs. + /// Details of the format is documented in + /// http://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#store%28java.io.Writer,%20java.lang.String%29 + /// + /// Properties to generate a string from. Each value must not + /// contain a newline. + /// String with Gradle (Java) properties + public static string GenerateGradleProperties(Dictionary properties) { + var lines = new List(); + foreach (var kv in properties) { + var escapedKey = kv.Key.Replace(" ", "\\ "); + var elementAfterLeadingWhitespace = kv.Value.TrimStart(new [] { ' ' }); + var escapedElement = + kv.Value.Substring(elementAfterLeadingWhitespace.Length).Replace(" ", "\\ ") + + EscapeGradlePropertyValue(elementAfterLeadingWhitespace); + lines.Add(String.Format("{0}={1}", escapedKey, escapedElement)); + } + return String.Join("\n", lines.ToArray()); + } + + /// + /// File scheme that can be concatenated with an absolute path on the local filesystem. + /// + public const string FILE_SCHEME = "file:///"; + + /// + /// Convert a local filesystem path to a URI. + /// + /// Path to convert. + /// File URI. + public static string PathToFileUri(string localPath) { + return FILE_SCHEME + FileUtils.PosixPathSeparators(Path.GetFullPath(localPath)); + } + + // Special characters that should not be escaped in URIs for Gradle property values. + private static HashSet GradleUriExcludeEscapeCharacters = new HashSet { + ":" + }; + + /// + /// Escape a URI so that it can be passed to Gradle. + /// + /// URI to escape. + /// Escaped URI. + public static string EscapeUri(string uri) { + // Escape the URI to handle special characters like spaces and percent escape + // all characters that are interpreted by gradle. + return GradleWrapper.EscapeGradlePropertyValue( + Uri.EscapeUriString(uri), escapeFunc: Uri.EscapeDataString, + charactersToExclude: GradleUriExcludeEscapeCharacters); + } + } +} diff --git a/source/PlayServicesResolver/src/JavaUtilities.cs b/source/AndroidResolver/src/JavaUtilities.cs similarity index 86% rename from source/PlayServicesResolver/src/JavaUtilities.cs rename to source/AndroidResolver/src/JavaUtilities.cs index eb896bd6..54b87beb 100644 --- a/source/PlayServicesResolver/src/JavaUtilities.cs +++ b/source/AndroidResolver/src/JavaUtilities.cs @@ -41,7 +41,7 @@ public ToolNotFoundException(string message) : base(message) {} /// /// Environment variable used to specify the Java distribution directory. /// - private const string JAVA_HOME = "JAVA_HOME"; + internal const string JAVA_HOME = "JAVA_HOME"; /// /// Minimum JDK version required to build with recently released Android libraries. @@ -52,9 +52,14 @@ public ToolNotFoundException(string message) : base(message) {} /// Find the JDK path (JAVA_HOME) either configured in the Unity editor or via the JAVA_HOME /// environment variable. /// - private static string JavaHome { + internal static string JavaHome { get { - var javaHome = UnityEditor.EditorPrefs.GetString("JdkPath"); + string javaHome = null; + // Unity 2019.3 added AndroidExternalToolsSettings which contains the JDK path so + // try to use that first. + var javaRootPath = UnityCompat.AndroidExternalToolsSettingsJdkRootPath; + if (!String.IsNullOrEmpty(javaRootPath)) javaHome = javaRootPath; + // Unity 2019.x added installation of the JDK in the AndroidPlayer directory // so fallback to searching for it there. if (String.IsNullOrEmpty(javaHome) || EditorPrefs.GetBool("JdkUseEmbedded")) { @@ -64,9 +69,20 @@ private static string JavaHome { "Editor", "").Replace("OSX", "MacOS"); var openJdkDir = Path.Combine(Path.Combine(Path.Combine( androidPlayerDir, "Tools"), "OpenJDK"), platformDir); - if (Directory.Exists(openJdkDir)) javaHome = openJdkDir; + if (Directory.Exists(openJdkDir)) { + javaHome = openJdkDir; + } else { + openJdkDir = Path.Combine(androidPlayerDir, "OpenJDK"); + if (Directory.Exists(openJdkDir)) javaHome = openJdkDir; + } } } + + // Pre Unity 2019, use the JDK path in the preferences. + if (String.IsNullOrEmpty(javaHome)) { + javaHome = UnityEditor.EditorPrefs.GetString("JdkPath"); + } + // If the JDK stil isn't found, check the environment. if (String.IsNullOrEmpty(javaHome)) { javaHome = Environment.GetEnvironmentVariable(JAVA_HOME); @@ -116,7 +132,7 @@ private static string JavaHomeBinaryPath(string javaTool) { /// /// Find a Java tool. /// - /// Name of the tool to search for. + /// Name of the tool to search for. /// Path to the tool if it's found, throws a ToolNotFoundException /// otherwise. private static string FindJavaTool(string javaTool) @@ -126,12 +142,12 @@ private static string FindJavaTool(string javaTool) if (!String.IsNullOrEmpty(javaHome)) { toolPath = JavaHomeBinaryPath(javaTool); if (String.IsNullOrEmpty(toolPath)) { - EditorUtility.DisplayDialog( + DialogWindow.Display( "Android Resolver", String.Format("{0} environment references a directory ({1}) that does " + "not contain {2} which is required to process Android " + "libraries.", JAVA_HOME, javaHome, javaTool), - "OK"); + DialogWindow.Option.Selected0, "OK"); throw new ToolNotFoundException( String.Format("{0} not found, {1} references incomplete Java distribution.", javaTool, javaHome)); @@ -140,14 +156,14 @@ private static string FindJavaTool(string javaTool) } else { toolPath = CommandLine.FindExecutable(javaTool); if (!File.Exists(toolPath)) { - EditorUtility.DisplayDialog( + DialogWindow.Display( "Android Resolver", String.Format("Unable to find {0} in the system path. This tool is " + "required to process Android libraries. Please configure " + "your JDK location under the " + "'Unity Preferences > External Tools' menu.", javaTool), - "OK"); + DialogWindow.Option.Selected0, "OK"); throw new ToolNotFoundException(javaTool + " not found."); } } @@ -163,7 +179,7 @@ private static void LogJdkVersionFailedWarning(string javaPath, string commandLi PlayServicesResolver.Log( String.Format( "Failed to get Java version when running {0}\n" + - "It is not be possible to verify your Java installation is new enough to " + + "It is not possible to verify your Java installation is new enough to " + "compile with the latest Android SDK\n\n" + "{1}", javaPath, commandLineSummary), level: LogLevel.Warning); @@ -210,6 +226,7 @@ internal static void CheckJdkForApiLevel() { } // If the user's installed JDK is too old, report an error. if (foundVersion < MinimumJdkVersion) { + PlayServicesResolver.analytics.Report("jdk/outofdate", "JDK out of date"); PlayServicesResolver.Log( String.Format("The configured JDK {0} is too old to build Android " + "applications with recent libraries.\n" + diff --git a/source/PlayServicesResolver/src/LocalMavenRepository.cs b/source/AndroidResolver/src/LocalMavenRepository.cs similarity index 71% rename from source/PlayServicesResolver/src/LocalMavenRepository.cs rename to source/AndroidResolver/src/LocalMavenRepository.cs index 129a0ee6..b4467ccf 100644 --- a/source/PlayServicesResolver/src/LocalMavenRepository.cs +++ b/source/AndroidResolver/src/LocalMavenRepository.cs @@ -36,11 +36,12 @@ internal class LocalMavenRepository { public static HashSet FindLocalRepos(ICollection dependencies) { // Find all repositories embedded in the project. var repos = new HashSet(); - foreach (var dependency in dependencies) { - foreach (var repo in dependency.Repositories) { - if (repo.Replace("\\", "/").ToLower().StartsWith("assets/")) { - repos.Add(repo); - } + var projectUri = GradleResolver.RepoPathToUri(Path.GetFullPath(".")); + foreach (var reposAndSources in + PlayServicesResolver.GetRepos(dependencies: dependencies)) { + var repoUri = reposAndSources.Key; + if (repoUri.StartsWith(projectUri)) { + repos.Add(Uri.UnescapeDataString(repoUri.Substring(projectUri.Length + 1))); } } return repos; @@ -53,12 +54,14 @@ public static HashSet FindLocalRepos(ICollection dependencie /// A list of found aar and srcaar files. public static List FindAars(string directory) { var foundFiles = new List(); - foreach (string filename in Directory.GetFiles(directory)) { - var packaging = Path.GetExtension(filename).ToLower(); - if (packaging == ".aar" || packaging == ".srcaar") foundFiles.Add(filename); - } - foreach (string currentDirectory in Directory.GetDirectories(directory)) { - foundFiles.AddRange(FindAars(currentDirectory)); + if (Directory.Exists(directory)) { + foreach (string filename in Directory.GetFiles(directory)) { + var packaging = Path.GetExtension(filename).ToLower(); + if (packaging == ".aar" || packaging == ".srcaar") foundFiles.Add(filename); + } + foreach (string currentDirectory in Directory.GetDirectories(directory)) { + foundFiles.AddRange(FindAars(currentDirectory)); + } } return foundFiles; } @@ -79,9 +82,9 @@ public static List FindAarsInLocalRepos(ICollection dependen /// /// Get a path without a filename extension. /// - /// Path to a file. + /// Path to a file. /// Path (including directory) without a filename extension. - private static string PathWithoutExtension(string path) { + internal static string PathWithoutExtension(string path) { return Path.Combine(Path.GetDirectoryName(path), Path.GetFileNameWithoutExtension(path)); } @@ -92,16 +95,37 @@ private static string PathWithoutExtension(string path) { /// file. /// /// artifactFilename + /// If artifactFilename is copied from a different location, + /// pass the original location where POM file lives. /// true if successful, false otherwise. - public static bool PatchPomFile(string artifactFilename) { + public static bool PatchPomFile(string artifactFilename, string sourceFilename) { + if (sourceFilename == null) { + sourceFilename = artifactFilename; + } + if (FileUtils.IsUnderPackageDirectory(artifactFilename)) { + // File under Packages folder is immutable. + PlayServicesResolver.Log( + String.Format("Cannot patch POM from Packages directory since it is immutable" + + " ({0})", artifactFilename), level: LogLevel.Error); + return false; + } + var failureImpact = String.Format("{0} may not be included in your project", Path.GetFileName(artifactFilename)); var pomFilename = PathWithoutExtension(artifactFilename) + ".pom"; - if (!File.Exists(pomFilename)) { - PlayServicesResolver.Log( - String.Format("Maven POM {0} for {1} does not exist. " + failureImpact, - pomFilename, artifactFilename), level: LogLevel.Warning); - return false; + // Copy POM file if artifact has been copied from a different location as well. + if (String.Compare(sourceFilename, artifactFilename) != 0 && + !File.Exists(pomFilename)) { + var sourcePomFilename = PathWithoutExtension(sourceFilename) + ".pom"; + var error = PlayServicesResolver.CopyAssetAndLabel( + sourcePomFilename, pomFilename); + if (!String.IsNullOrEmpty(error)) { + PlayServicesResolver.Log( + String.Format("Failed to copy POM from {0} to {1} due to:\n{2}", + sourcePomFilename, pomFilename, error), + level: LogLevel.Error); + return false; + } } var artifactPackaging = Path.GetExtension(artifactFilename).ToLower().Substring(1); var pom = new XmlDocument(); @@ -126,6 +150,12 @@ public static bool PatchPomFile(string artifactFilename) { updatedPackaging = true; } } + if (!FileUtils.CheckoutFile(pomFilename, PlayServicesResolver.logger)) { + PlayServicesResolver.Log( + String.Format("Unable to checkout '{0}' to patch the file for inclusion in a " + + "Gradle project.", pomFilename), LogLevel.Error); + return false; + } if (updatedPackaging) { try { using (var xmlWriter = @@ -159,6 +189,10 @@ public static bool PatchPomFilesInLocalRepos(ICollection dependencie // Filename extensions by the basename of each file path. var extensionsByBasenames = new Dictionary>(); foreach (var filename in FindAarsInLocalRepos(dependencies)) { + // No need to patch POM under package folder. + if (FileUtils.IsUnderPackageDirectory(filename)) { + continue; + } var pathWithoutExtension = PathWithoutExtension(filename); HashSet extensions; if (!extensionsByBasenames.TryGetValue(pathWithoutExtension, out extensions)) { @@ -182,7 +216,8 @@ public static bool PatchPomFilesInLocalRepos(ICollection dependencie } if (foundFile) break; } - successful &= PatchPomFile(kv.Key + filePackagingToUse); + var artifect = kv.Key + filePackagingToUse; + successful &= PatchPomFile(artifect, artifect); } return successful; } diff --git a/source/PlayServicesResolver/src/PlayServicesPreBuild.cs b/source/AndroidResolver/src/PlayServicesPreBuild.cs similarity index 90% rename from source/PlayServicesResolver/src/PlayServicesPreBuild.cs rename to source/AndroidResolver/src/PlayServicesPreBuild.cs index fd5012f3..21c28cbd 100644 --- a/source/PlayServicesResolver/src/PlayServicesPreBuild.cs +++ b/source/AndroidResolver/src/PlayServicesPreBuild.cs @@ -23,11 +23,11 @@ private static void WarnIfAutoResolveDisabled() { "Ensure you have run the resolver manually." + "\n\nWith auto-resolution of Android dependencies disabled you " + "must manually resolve dependencies using the " + - "\"Assets > Play Services Resolver > Android Resolver > " + + "\"Assets > External Dependency Manager > Android Resolver > " + "Resolve\" menu item.\n\nFailure to resolve Android " + "dependencies will result in an non-functional " + "application.\nTo enable auto-resolution, navigate to " + - "\"Assets > Play Services Resolver > Android Resolver > " + + "\"Assets > External Dependency Manager > Android Resolver > " + "Settings\" and check \"Enable Auto-resolution\""); } diff --git a/source/PlayServicesResolver/src/PlayServicesResolver.cs b/source/AndroidResolver/src/PlayServicesResolver.cs similarity index 51% rename from source/PlayServicesResolver/src/PlayServicesResolver.cs rename to source/AndroidResolver/src/PlayServicesResolver.cs index 7f13ebf9..e4f7d40e 100644 --- a/source/PlayServicesResolver/src/PlayServicesResolver.cs +++ b/source/AndroidResolver/src/PlayServicesResolver.cs @@ -18,6 +18,7 @@ namespace GooglePlayServices { using System; using System.Collections.Generic; using System.IO; + using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Xml; @@ -25,6 +26,11 @@ namespace GooglePlayServices { using Google.JarResolver; using UnityEditor; using UnityEngine; + // Unforunately, SettingsDialog is a public method of this object so collides with + // GooglePlayServices.SettingsDialog when used from this class, so alias this as + // SettingsDialogObj. + using SettingsDialogObj = GooglePlayServices.SettingsDialog; + /// /// Play services resolver. This is a background post processor @@ -58,6 +64,11 @@ internal class DependencyState { /// public HashSet Files { get; internal set; } + /// + /// Settings used to resolve these dependencies. + /// + public Dictionary Settings { get; internal set; } + /// /// Determine the current state of the project. /// @@ -66,7 +77,8 @@ internal class DependencyState { public static DependencyState GetState() { return new DependencyState { Packages = new HashSet(PlayServicesSupport.GetAllDependencies().Keys), - Files = new HashSet(PlayServicesResolver.FindLabeledAssets()) + Files = new HashSet(PlayServicesResolver.FindLabeledAssets()), + Settings = PlayServicesResolver.GetResolutionSettings(), }; } @@ -74,7 +86,7 @@ public static DependencyState GetState() { /// Sort a string hashset. /// /// Set to sort and return via an enumerable. - private static IEnumerable SortSet(HashSet setToSort) { + private static IEnumerable SortSet(IEnumerable setToSort) { var sorted = new SortedDictionary(); foreach (var value in setToSort) sorted[value] = true; return sorted.Keys; @@ -86,6 +98,13 @@ private static IEnumerable SortSet(HashSet setToSort) { public void WriteToFile() { try { Directory.CreateDirectory(Path.GetDirectoryName(DEPENDENCY_STATE_FILE)); + if (!FileUtils.CheckoutFile(DEPENDENCY_STATE_FILE, logger)) { + logger.Log(String.Format( + "Unable to checkout '{0}'. Resolution results can't be saved, " + + "disabling auto-resolution.", DEPENDENCY_STATE_FILE), LogLevel.Error); + SettingsDialogObj.EnableAutoResolution = false; + return; + } using (var writer = new XmlTextWriter(new StreamWriter(DEPENDENCY_STATE_FILE)) { Formatting = Formatting.Indented, }) { @@ -100,7 +119,15 @@ public void WriteToFile() { writer.WriteStartElement("files"); foreach (var assetPath in SortSet(Files)) { writer.WriteStartElement("file"); - writer.WriteValue(assetPath); + writer.WriteValue(FileUtils.PosixPathSeparators(assetPath)); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + writer.WriteStartElement("settings"); + foreach (var settingKey in SortSet(Settings.Keys)) { + writer.WriteStartElement("setting"); + writer.WriteAttributeString("name", settingKey); + writer.WriteAttributeString("value", Settings[settingKey]); writer.WriteEndElement(); } writer.WriteEndElement(); @@ -138,13 +165,15 @@ public void WriteToFile() { public static DependencyState ReadFromFile() { var packages = new HashSet(); var files = new HashSet(); + var settings = new Dictionary(); if (!XmlUtilities.ParseXmlTextFileElements( DEPENDENCY_STATE_FILE, logger, (reader, elementName, isStart, parentElementName, elementNameStack) => { if (isStart) { if (elementName == "dependencies" && parentElementName == "") { return true; - } else if ((elementName == "packages" || elementName == "files") && + } else if ((elementName == "packages" || elementName == "files" || + elementName == "settings") && parentElementName == "dependencies") { return true; } else if (elementName == "package" && @@ -160,15 +189,27 @@ public static DependencyState ReadFromFile() { files.Add(reader.ReadContentAsString()); } return true; + } else if (elementName == "setting" && + parentElementName == "settings") { + if (isStart) { + string name = reader.GetAttribute("name"); + string value = reader.GetAttribute("value"); + if (!String.IsNullOrEmpty(name) && + !String.IsNullOrEmpty(value)) { + settings[name] = value; + } + } + return true; } } - return false; + return true; })) { return null; } - return new DependencyState { + return new DependencyState() { Packages = packages, - Files = files + Files = files, + Settings = settings }; } @@ -179,7 +220,16 @@ public static DependencyState ReadFromFile() { /// true if both objects have the same contents, false otherwise. public override bool Equals(System.Object obj) { var state = obj as DependencyState; - return state != null && Packages.SetEquals(state.Packages) && + bool settingsTheSame = state != null && Settings.Count == state.Settings.Count; + if (settingsTheSame) { + foreach (var kv in Settings) { + string value; + settingsTheSame = state.Settings.TryGetValue(kv.Key, out value) && + value == kv.Value; + if (!settingsTheSame) break; + } + } + return settingsTheSame && Packages.SetEquals(state.Packages) && Files.SetEquals(state.Files); } @@ -195,6 +245,9 @@ public override int GetHashCode() { foreach (var pkg in Packages) { hash ^= pkg.GetHashCode(); } + foreach (var setting in Settings.Values) { + hash ^= setting.GetHashCode(); + } return hash; } @@ -206,13 +259,26 @@ private static string SetToString(HashSet setToConvert) { return String.Join(", ", (new List(SortSet(setToConvert))).ToArray()); } + /// + /// Convert a dictionary to a sorted comma separated string. + /// + /// Comma separated string in the form key=value. + private static string DictionaryToString(Dictionary dict) { + var components = new List(); + foreach (var key in SortSet(dict.Keys)) { + components.Add(String.Format("{0}={1}", key, dict[key])); + } + return String.Join(", ", components.ToArray()); + } + /// /// Display dependencies as a string. /// /// Human readable string. public override string ToString() { - return String.Format("packages=({0}), files=({1})", SetToString(Packages), - SetToString(Files)); + return String.Format("packages=({0}), files=({1}) settings=({2})", + SetToString(Packages), SetToString(Files), + DictionaryToString(Settings)); } } @@ -220,7 +286,7 @@ public override string ToString() { /// Polls a value and signals a callback with the change after the specified delay /// time. /// - private class PropertyPoller { + internal class PropertyPoller { /// /// Delegate that is called when a value changes. /// @@ -230,6 +296,8 @@ private class PropertyPoller { /// changed. public delegate void Changed(T previousValue, T currentValue); + // Whether the previous value has been initialized. + private bool previousValueInitialized = false; // Previous value of the property. private T previousValue = default(T); // Previous value of the property when it was last polled. @@ -248,16 +316,14 @@ private class PropertyPoller { /// /// Create the poller. /// - /// Initial value of the property being polled. /// Name of the property being polled. /// Time to wait before signalling that the value /// has changed. /// Time to check the value of the property for - /// changes. - public PropertyPoller(T initialValue, string propertyName, + /// changes. + public PropertyPoller(string propertyName, int delayTimeInSeconds = 3, int checkIntervalInSeconds = 1) { - previousValue = initialValue; this.propertyName = propertyName; this.delayTimeInSeconds = delayTimeInSeconds; this.checkIntervalInSeconds = checkIntervalInSeconds; @@ -276,6 +342,13 @@ public void Poll(Func getCurrentValue, Changed changed) { } previousCheckTime = currentTime; T currentValue = getCurrentValue(); + // If the poller isn't initailized, store the current value before polling for + // changes. + if (!previousValueInitialized) { + previousValueInitialized = true; + previousValue = currentValue; + return; + } if (!currentValue.Equals(previousValue)) { if (currentValue.Equals(previousPollValue)) { if (currentTime.Subtract(previousPollTime).TotalSeconds >= @@ -294,25 +367,39 @@ public void Poll(Func getCurrentValue, Changed changed) { } } + /// + /// Namespace for embedded resources packaged from the PlayServicesResolver/scripts + /// directory. + /// + internal const string EMBEDDED_RESOURCES_NAMESPACE = "PlayServicesResolver.scripts."; + + // Silence the unused variable warning, as this holds a reference to the + // PlayServicesSupport singleton. + #pragma warning disable 414 /// /// The instance to the play services support object. /// private static PlayServicesSupport svcSupport; + #pragma warning restore 414 - /// - /// Selects between different types of IResolver implementations that can be used. + /// + /// Resolver that uses Gradle to download libraries and embed them within a Unity project. + /// Lazy initialize it only when current build target is Android. /// - public enum ResolverType - { - Default, // Standard versioned resolver + private static GradleResolver GradleResolverInstance { + get { + if (gradleResolverInstance == null && + EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) { + gradleResolverInstance = new GradleResolver(); + } + return gradleResolverInstance; + } } /// - /// The resolver to use, injected to allow for version updating. + /// Instance of GradleResolver. /// - private static Dictionary _resolvers = - new Dictionary(); - + private static GradleResolver gradleResolverInstance = null; /// /// Resoluton job. @@ -363,8 +450,8 @@ public ResolutionJob(bool isAutoResolveJob, Action job) { /// /// Polls for changes in the bundle ID. /// - private static PropertyPoller bundleIdPoller = new PropertyPoller( - GetAndroidApplicationId(), "Bundle ID"); + private static PropertyPoller bundleIdPoller = + new PropertyPoller("Bundle ID"); /// /// Arguments for the bundle ID update event. @@ -386,16 +473,10 @@ public class BundleIdChangedEventArgs : EventArgs { /// public static event EventHandler BundleIdChanged; - /// - /// Value of the InstallAndroidPackages before settings were changed. - /// - private static bool previousInstallAndroidPackages = - GooglePlayServices.SettingsDialog.InstallAndroidPackages; - /// /// Asset label applied to files managed by this plugin. /// - private const string ManagedAssetLabel = "gpsr"; + internal const string ManagedAssetLabel = "gpsr"; /// /// Get a boolean property from UnityEditor.EditorUserBuildSettings. @@ -432,6 +513,140 @@ public static bool GradleTemplateEnabled { } } + /// + /// Whether the Gradle Properties template is enabled. (Unity 2019.3 and above) + /// + public static bool GradlePropertiesTemplateEnabled { + get { + return GradleBuildEnabled && + File.Exists(GradleTemplateResolver.GradlePropertiesTemplatePath); + } + } + + /// + /// Whether the Gradle Settings template is enabled. + /// + public static bool GradleSettingsTemplateEnabled { + get { + return GradleBuildEnabled && + File.Exists(GradleTemplateResolver.GradleSettingsTemplatePath); + } + } + + // Backing store for GradleVersion property. + private static string gradleVersion = null; + // Extracts a version number from a gradle distribution jar file. + private static Regex gradleJarVersionExtract = new Regex(@"^gradle-core-([0-9.]+)\.jar$"); + + /// + /// Get / set the Gradle version. + /// This property is populated when it's first read by parsing the version number of the + /// gradle-core-*.jar in the AndroidPlayer directory. + /// + public static string GradleVersion { + set { gradleVersion = value; } + get { + if (!String.IsNullOrEmpty(gradleVersion)) return gradleVersion; + string gradleLibDir = null; + + // Unity 2019.3 added AndroidExternalToolsSettings which contains the Gradle path + // so try to use that first. + var gradleDir = UnityCompat.AndroidExternalToolsSettingsGradlePath; + if (Directory.Exists(gradleDir)) gradleLibDir = Path.Combine(gradleDir, "lib"); + + // Fallback to searching for gradle redistributed with Unity in the Android plugin. + if (String.IsNullOrEmpty(gradleDir)) { + var engineDir = AndroidPlaybackEngineDirectory; + if (String.IsNullOrEmpty(engineDir)) return null; + gradleLibDir = Path.Combine(Path.Combine(Path.Combine(engineDir, "Tools"), + "gradle"), "lib"); + } + + if (!String.IsNullOrEmpty(gradleLibDir) && Directory.Exists(gradleLibDir)) { + foreach (var path in Directory.GetFiles(gradleLibDir, "gradle-core-*.jar", + SearchOption.TopDirectoryOnly)) { + var match = gradleJarVersionExtract.Match(Path.GetFileName(path)); + if (match != null && match.Success) { + gradleVersion = match.Result("$1"); + break; + } + } + } + return gradleVersion; + } + } + + // Backing store for the AndroidGradlePluginVersion property. + private static string androidGradlePluginVersion = null; + // Modification time of mainTemplate.gradle the last time it was searched for the Android + // Gradle plugin version. + private static DateTime mainTemplateLastWriteTime = default(DateTime); + // Extracts an Android Gradle Plugin version number from the contents of a *.gradle file. + // This should work for Unity 2022.1 and below. + // Ex. + // classpath 'com.android.tools.build:gradle:4.0.1' + private static Regex androidGradlePluginVersionExtract_legacy = new Regex( + @"['""]com\.android\.tools\.build:gradle:([^'""]+)['""]$"); + // Extracts an Android Gradle Plugin version number from the contents of a *.gradle file for + // Unity 2022.2+ or 2023.1+. + // Ex. + // id 'com.android.application' version '7.1.2' apply false + private static Regex androidGradlePluginVersionExtract = new Regex( + @"['""]com\.android\.application['""] version ['""]([^'""]+)['""]"); + + /// + /// Get the Android Gradle Plugin version used by Unity. + /// + public static string AndroidGradlePluginVersion { + set { + androidGradlePluginVersion = value; + mainTemplateLastWriteTime = DateTime.Now; + } + get { + // If the gradle template changed, read the plugin version again. + var mainTemplateGradlePath = GradleTemplateResolver.GradleTemplatePath; + if (File.Exists(mainTemplateGradlePath)) { + var lastWriteTime = File.GetLastWriteTime(mainTemplateGradlePath); + if (lastWriteTime.CompareTo(mainTemplateLastWriteTime) > 0) { + androidGradlePluginVersion = null; + } + } + // If the plugin version is cached, return it. + if (!String.IsNullOrEmpty(androidGradlePluginVersion)) { + return androidGradlePluginVersion; + } + // Search the gradle templates for the plugin version. + var gradleTemplateDir = GradleTemplateResolver.UnityGradleTemplatesDir; + if (Directory.Exists(gradleTemplateDir)) { + var gradleTemplates = new List(); + if (File.Exists(mainTemplateGradlePath)) { + gradleTemplates.Add(mainTemplateGradlePath); + } + gradleTemplates.AddRange(Directory.GetFiles(gradleTemplateDir, "*.gradle", + SearchOption.TopDirectoryOnly)); + foreach (var path in gradleTemplates) { + foreach (var line in File.ReadAllLines(path)) { + var match = androidGradlePluginVersionExtract_legacy.Match(line); + if (match != null && match.Success) { + androidGradlePluginVersion = match.Result("$1"); + break; + } + match = androidGradlePluginVersionExtract.Match(line); + if (match != null && match.Success) { + androidGradlePluginVersion = match.Result("$1"); + break; + } + } + if (!String.IsNullOrEmpty(androidGradlePluginVersion)) break; + } + } + Log(String.Format("Detected Android Gradle Plugin Version {0}.", + androidGradlePluginVersion), + level: LogLevel.Verbose); + return androidGradlePluginVersion; + } + } + /// /// Whether project export is enabled. /// @@ -458,7 +673,7 @@ public static bool GradleProjectExportEnabled { /// private struct AndroidBuildSystemSettings { /// - // Whether the Gradle build is enabled. + /// Whether the Gradle build is enabled. /// public bool GradleBuildEnabled { get; private set; } @@ -467,6 +682,11 @@ private struct AndroidBuildSystemSettings { /// public bool GradleTemplateEnabled { get; private set; } + /// + /// Whether the Gradle properties template is enabled. + /// + public bool GradlePropertiesTemplateEnabled { get; private set; } + /// // Whether project export is enabled. /// @@ -480,13 +700,14 @@ public static AndroidBuildSystemSettings Current { return new AndroidBuildSystemSettings { GradleBuildEnabled = PlayServicesResolver.GradleBuildEnabled, GradleTemplateEnabled = PlayServicesResolver.GradleTemplateEnabled, + GradlePropertiesTemplateEnabled = PlayServicesResolver.GradlePropertiesTemplateEnabled, ProjectExportEnabled = PlayServicesResolver.ProjectExportEnabled }; } } /// - // Compare with another AndroidBuildSystemSettings. + /// Compare with another AndroidBuildSystemSettings. /// /// Object to compare with. /// true if the object is the same as this, false otherwise. @@ -494,6 +715,7 @@ public override bool Equals(System.Object obj) { var other = (AndroidBuildSystemSettings)obj; return other.GradleBuildEnabled == GradleBuildEnabled && other.GradleTemplateEnabled == GradleTemplateEnabled && + other.GradlePropertiesTemplateEnabled == GradlePropertiesTemplateEnabled && other.ProjectExportEnabled == ProjectExportEnabled; } @@ -503,7 +725,8 @@ public override bool Equals(System.Object obj) { /// Hash of this object. public override int GetHashCode() { return GradleBuildEnabled.GetHashCode() ^ GradleTemplateEnabled.GetHashCode() ^ - ProjectExportEnabled.GetHashCode(); + GradlePropertiesTemplateEnabled.GetHashCode() ^ + ProjectExportEnabled.GetHashCode(); } @@ -513,9 +736,9 @@ public override int GetHashCode() { /// String representation. public override string ToString() { return String.Format("[GradleBuildEnabled={0} GradleTemplateEnabled={1} " + - "ProjectExportEnabled={2}]", + "GradlePropertiesTemplateEnabled={2} ProjectExportEnabled={2}]", GradleBuildEnabled, GradleTemplateEnabled, - ProjectExportEnabled); + GradlePropertiesTemplateEnabled, ProjectExportEnabled); } } @@ -523,8 +746,7 @@ public override string ToString() { /// Polls for changes in build system settings. /// private static PropertyPoller androidBuildSystemPoller = - new PropertyPoller( - AndroidBuildSystemSettings.Current, "Android Build Settings"); + new PropertyPoller("Android Build Settings"); /// /// Arguments for the Android build system changed event. @@ -551,6 +773,22 @@ public class AndroidBuildSystemChangedArgs : EventArgs { /// public bool PreviousGradleTemplateEnabled { get; set; } + /// + /// Project export was selected when this event was fired. + /// + + /// + /// Whether a custom Gradle Properties template is enabled. + /// This will only be true if GradleBuildEnabled is also true. + /// + public bool GradlePropertiesTemplateEnabled { get; set; } + + /// + /// Whether a custom Gradle Properties Template + /// was enabled the last time this event was fired. + /// + public bool PreviousGradlePropertiesTemplateEnabled { get; set; } + /// /// Project export was selected when this event was fired. /// @@ -571,13 +809,26 @@ public class AndroidBuildSystemChangedArgs : EventArgs { /// Polls for changes in the Android device ABI. /// private static PropertyPoller androidAbisPoller = - new PropertyPoller(new AndroidAbis(), "Android Target Device ABI"); + new PropertyPoller("Android Target Device ABI"); /// /// Logger for this module. /// internal static Google.Logger logger = new Google.Logger(); + // Analytics reporter. + internal static EditorMeasurement analytics = new EditorMeasurement( + GooglePlayServices.SettingsDialog.projectSettings, logger, + VersionHandlerImpl.GA_TRACKING_ID, VersionHandlerImpl.MEASUREMENT_ID, + VersionHandlerImpl.PLUGIN_SUITE_NAME, "", VersionHandlerImpl.PRIVACY_POLICY) { + BasePath = "/androidresolver/", + BaseQuery = String.Format("version={0}", AndroidResolverVersionNumber.Value.ToString()), + BaseReportName = "Android Resolver: ", + InstallSourceFilename = + System.Reflection.Assembly.GetAssembly(typeof(PlayServicesResolver)).Location, + DataUsageUrl = VersionHandlerImpl.DATA_USAGE_URL + }; + /// /// Arguments for the Android build system changed event. /// @@ -609,7 +860,14 @@ public class AndroidAbisChangedArgs : EventArgs { /// public static string AndroidSdkRoot { get { - var sdkPath = EditorPrefs.GetString("AndroidSdkRoot"); + string sdkPath = null; + // Unity 2019.3 added AndroidExternalToolsSettings which contains the Android SDK + // path so try to use that first. + var androidSdkRootPath = UnityCompat.AndroidExternalToolsSettingsSdkRootPath; + if (!String.IsNullOrEmpty(androidSdkRootPath)) { + if (Directory.Exists(androidSdkRootPath)) sdkPath = androidSdkRootPath; + } + // Unity 2019.x added installation of the Android SDK in the AndroidPlayer directory // so fallback to searching for it there. if (String.IsNullOrEmpty(sdkPath) || EditorPrefs.GetBool("SdkUseEmbedded")) { @@ -619,6 +877,11 @@ public static string AndroidSdkRoot { if (Directory.Exists(androidPlayerSdkDir)) sdkPath = androidPlayerSdkDir; } } + + // Pre Unity 2019 Android SDK path. + if (String.IsNullOrEmpty(sdkPath)) { + sdkPath = EditorPrefs.GetString("AndroidSdkRoot"); + } return sdkPath; } } @@ -627,7 +890,7 @@ public static string AndroidSdkRoot { /// Polls for changes in AndroidSdkRoot. /// private static PropertyPoller androidSdkRootPoller = - new PropertyPoller(AndroidSdkRoot, "Android SDK Path"); + new PropertyPoller("Android SDK Path"); /// /// Arguments for the AndroidSdkRootChanged event. @@ -649,19 +912,59 @@ public class AndroidSdkRootChangedArgs : EventArgs { /// public static event EventHandler AndroidSdkRootChanged; + // Backing store to cache AndroidPlaybackEngineDirectory. + // This is set to either null (Android player not installed) or the path of the + // playback engine directory when AndroidPlaybackEngineDirectory is first accessed. + private static string androidPlaybackEngineDirectory = ""; + /// /// Get the Android playback engine directory. /// /// Get the playback engine directory. public static string AndroidPlaybackEngineDirectory { get { - try { - return (string)VersionHandler.InvokeStaticMethod( - typeof(BuildPipeline), "GetPlaybackEngineDirectory", - new object[] { BuildTarget.Android, BuildOptions.None }); - } catch (Exception) { - return null; + if (androidPlaybackEngineDirectory != null && + androidPlaybackEngineDirectory == "") { + try { + androidPlaybackEngineDirectory = + (string)VersionHandler.InvokeStaticMethod( + typeof(BuildPipeline), "GetPlaybackEngineDirectory", + new object[] { BuildTarget.Android, BuildOptions.None }); + } catch (Exception) { + androidPlaybackEngineDirectory = null; + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { + if (assembly.GetName().Name == "UnityEditor.Android.Extensions") { + androidPlaybackEngineDirectory = + Path.GetDirectoryName(assembly.Location); + break; + } + } + } } + return androidPlaybackEngineDirectory; + } + } + + // Backing store for the GradleWrapper property. + private static GradleWrapper gradleWrapper = new GradleWrapper( + typeof(PlayServicesResolver).Assembly, + PlayServicesResolver.EMBEDDED_RESOURCES_NAMESPACE + "gradle-template.zip", + Path.Combine(FileUtils.ProjectTemporaryDirectory, "PlayServicesResolverGradle")); + + /// + /// Class to interface with the embedded Gradle wrapper. + /// + internal static GradleWrapper Gradle { get { return gradleWrapper; } } + + /// + /// Returns true if automatic resolution is enabled. + /// Auto-resolution is never enabled in batch mode. Each build setting change must be + /// manually followed by DoResolution(). + /// + public static bool AutomaticResolutionEnabled { + get { + return SettingsDialogObj.EnableAutoResolution && + !ExecutionEnvironment.InBatchMode; } } @@ -669,18 +972,35 @@ public static string AndroidPlaybackEngineDirectory { /// Initializes the class. /// static PlayServicesResolver() { - // Create the resolver. - if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) { - RegisterResolver(new ResolverVer1_1()); - // Monitor Android dependency XML files to perform auto-resolution. - AddAutoResolutionFilePatterns(xmlDependencies.fileRegularExpressions); - - svcSupport = PlayServicesSupport.CreateInstance( - "PlayServicesResolver", - AndroidSdkRoot, - "ProjectSettings", - logMessageWithLevel: LogDelegate); + // Delay initialization until the build target is Android and the editor is not in play + // mode. + EditorInitializer.InitializeOnMainThread(condition: () => { + return EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android && + !EditorApplication.isPlayingOrWillChangePlaymode; + }, initializer: Initialize, name: "PlayServicesResolver", logger: logger); + + } + + /// + /// Initialize the module. This should be called on the main thread only if + /// current active build target is Android and not in play mode. + /// + private static bool Initialize() { + if ( EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android ) { + throw new Exception("PlayServiceResolver.Initialize() is called when active " + + "build target is not Android. This should never happen. If it does, " + + "please report to the developer."); } + + // Monitor Android dependency XML files to perform auto-resolution. + AddAutoResolutionFilePatterns(xmlDependencies.fileRegularExpressions); + + svcSupport = PlayServicesSupport.CreateInstance( + "PlayServicesResolver", + AndroidSdkRoot, + "ProjectSettings", + logMessageWithLevel: LogDelegate); + RunOnMainThread.OnUpdate -= PollBundleId; RunOnMainThread.OnUpdate += PollBundleId; @@ -688,15 +1008,15 @@ static PlayServicesResolver() { OnSettingsChanged(); // Setup events for auto resolution. - if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) { - BundleIdChanged += ResolveOnBundleIdChanged; - AndroidBuildSystemChanged += ResolveOnBuildSystemChanged; - AndroidAbisChanged += ResolveOnAndroidAbisChanged; - AndroidSdkRootChanged += ResolveOnAndroidSdkRootChange; - ScheduleAutoResolve(); - } + BundleIdChanged += ResolveOnBundleIdChanged; + AndroidBuildSystemChanged += ResolveOnBuildSystemChanged; + AndroidAbisChanged += ResolveOnAndroidAbisChanged; + AndroidSdkRootChanged += ResolveOnAndroidSdkRootChange; + Reresolve(); - if (GooglePlayServices.SettingsDialog.EnableAutoResolution) LinkAutoResolution(); + if (SettingsDialogObj.EnableAutoResolution) LinkAutoResolution(); + + return true; } // Unregister events to monitor build system changes for the Android Resolver and other @@ -757,48 +1077,6 @@ internal static void Log(string message, Google.LogLevel level = LogLevel.Info) logger.Log(message, level: level); } - /// - /// Registers the resolver. - /// - /// - /// The resolver with the greatest version number is retained - /// - /// The resolver. - /// Resolver impl. - public static IResolver RegisterResolver(IResolver resolverImpl, - ResolverType resolverType = ResolverType.Default) { - if (resolverImpl == null) { - return Resolver; - } - - IResolver destResolver; - if (!_resolvers.TryGetValue(resolverType, out destResolver) || - destResolver.Version() < resolverImpl.Version()) { - _resolvers[resolverType] = resolverImpl; - } - return resolverImpl; - } - - private static ResolverType CurrentResolverType { - get { return ResolverType.Default; } - } - - /// - /// Gets the resolver. - /// - /// The resolver. - public static IResolver Resolver { - get { - IResolver resolver = null; - if (resolver == null) { - _resolvers.TryGetValue(ResolverType.Default, out resolver); - } - - return resolver; - } - } - - /// /// Patterns of files that are monitored to trigger auto resolution. /// @@ -832,7 +1110,7 @@ private static bool CheckFilesForAutoResolution(HashSet filesToCheck) { } } } - if (resolve) ScheduleAutoResolve(); + if (resolve) Reresolve(); return resolve; } @@ -849,10 +1127,10 @@ private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { - if (Resolver != null) { + if (GradleResolverInstance != null) { // If the manifest changed, try patching it. var manifestPath = FileUtils.NormalizePathSeparators( - GooglePlayServices.SettingsDialog.AndroidManifestPath); + SettingsDialogObj.AndroidManifestPath); foreach (var importedAsset in importedAssets) { if (FileUtils.NormalizePathSeparators(importedAsset) == manifestPath) { PatchAndroidManifest(GetAndroidApplicationId(), null); @@ -860,12 +1138,12 @@ private static void OnPostprocessAllAssets(string[] importedAssets, } } - if (Resolver.AutomaticResolutionEnabled()) { + if (AutomaticResolutionEnabled) { // If anything has been removed from the packaging directory schedule // resolution. foreach (string asset in deletedAssets) { - if (asset.StartsWith(GooglePlayServices.SettingsDialog.PackageDir)) { - ScheduleAutoResolve(); + if (asset.StartsWith(SettingsDialogObj.PackageDir)) { + Reresolve(); return; } } @@ -898,6 +1176,18 @@ private static void OnPostprocessAllAssets(string[] importedAssets, /// private static int scenesProcessed = 0; + /// + /// Reset static variable after every successful build. + /// + [UnityEditor.Callbacks.PostProcessBuildAttribute(0)] + private static void OnPostProcessBuild(UnityEditor.BuildTarget target, string path) { + // Since certain versions of Unity do not reload the module, we want + // to reset this variable in order to allow an attempt to resolve in OnPostProcessScene(). + // Caveat: Unity calls this only after a successful build. We should really be calling + // this after every build (successful or not). + scenesProcessed = 0; + } + /// /// If auto-resolution is enabled, run resolution synchronously before building the /// application. @@ -908,7 +1198,7 @@ private static void OnPostProcessScene() { if (UnityEngine.Application.isPlaying) return; // If the Android resolver isn't enabled or automatic resolution is disabled, // do nothing. - if (Resolver == null || !GooglePlayServices.SettingsDialog.AutoResolveOnBuild) { + if (GradleResolverInstance == null || !SettingsDialogObj.AutoResolveOnBuild) { return; } // If post-processing has already been executed since this module was loaded, don't @@ -916,7 +1206,9 @@ private static void OnPostProcessScene() { scenesProcessed++; if (scenesProcessed > 1) return; Log("Starting auto-resolution before scene build...", level: LogLevel.Verbose); - bool success = ResolveSync(false); + bool success = ResolveSync(false, true); + // If resolve fails, we want to try the resolve next time around. + if(!success) scenesProcessed--; Log(String.Format("Android resolution {0}.", success ? "succeeded" : "failed"), level: LogLevel.Verbose); } @@ -962,24 +1254,24 @@ private static void ScheduleAutoResolve(double delayInMilliseconds = 1000.0) { /// /// Called when resolution is complete. private static void AutoResolve(Action resolutionComplete) { - if (Resolver.AutomaticResolutionEnabled()) { + if (AutomaticResolutionEnabled) { ScheduleResolve( - false, (success) => { + false, false, (success) => { if (resolutionComplete != null) resolutionComplete(); }, true); } else if (!ExecutionEnvironment.InBatchMode && - GooglePlayServices.SettingsDialog.AutoResolutionDisabledWarning && + SettingsDialogObj.AutoResolutionDisabledWarning && PlayServicesSupport.GetAllDependencies().Count > 0) { Debug.LogWarning("Warning: Auto-resolution of Android dependencies is disabled! " + "Ensure you have manually run the resolver before building." + "\n\nWith auto-resolution of Android dependencies disabled you " + "must manually resolve dependencies using the " + - "\"Assets > Play Services Resolver > Android Resolver > " + + "\"Assets > External Dependency Manager > Android Resolver > " + "Resolve\" menu item.\n\nFailure to resolve Android " + "dependencies will result in an non-functional " + "application.\n\nTo enable auto-resolution, navigate to " + - "\"Assets > Play Services Resolver > Android Resolver > " + + "\"Assets > External Dependency Manager > Android Resolver > " + "Settings\" and check \"Enable Auto-resolution\""); resolutionComplete(); } @@ -989,8 +1281,8 @@ private static void AutoResolve(Action resolutionComplete) { /// Auto-resolve if any packages need to be resolved. /// private static void Reresolve() { - if (Resolver.AutomaticResolutionEnabled()) { - if (DeleteFiles(Resolver.OnBuildSettings())) ScheduleAutoResolve(); + if (AutomaticResolutionEnabled && GradleResolverInstance != null) { + ScheduleAutoResolve(); } } @@ -1008,7 +1300,7 @@ private static void Reresolve() { /// * foo.com.my.app.service --> foo.com.my.app.service (unchanged) /// /// Path of this node in the hierarchy of nodes. For example: - /// given node is "" in "" this should be "a/b/c". If this + /// given node is "<c>" in "<a><b><c>" this should be "a/b/c". If this /// value is null the name of the current node is used. /// true if any replacements are applied, false otherwise. private static bool ReplaceVariablesInXmlElementTree( @@ -1129,7 +1421,9 @@ internal static void ReplaceVariablesInAndroidManifest( /// Apply variable expansion in the AndroidManifest.xml file. /// private static void PatchAndroidManifest(string bundleId, string previousBundleId) { - if (!GooglePlayServices.SettingsDialog.PatchAndroidManifest) return; + if (!SettingsDialogObj.PatchAndroidManifest) return; + PlayServicesResolver.analytics.Report("/patchandroidmanifest", + "Patch AndroidManifest.xml"); // We only need to patch the manifest in Unity 2018 and above. if (Google.VersionHandler.GetUnityVersionMajorMinor() < 2018.0f) return; var replacements = new Dictionary(); @@ -1139,7 +1433,7 @@ private static void PatchAndroidManifest(string bundleId, string previousBundleI if (!(String.IsNullOrEmpty(previousBundleId) || String.IsNullOrEmpty(bundleId))) { replacements[previousBundleId] = bundleId; } - ReplaceVariablesInAndroidManifest(GooglePlayServices.SettingsDialog.AndroidManifestPath, + ReplaceVariablesInAndroidManifest(SettingsDialogObj.AndroidManifestPath, bundleId, replacements); } @@ -1187,6 +1481,8 @@ private static void PollBuildSystem() { PreviousGradleBuildEnabled = previousValue.GradleBuildEnabled, GradleTemplateEnabled = currentValue.GradleTemplateEnabled, PreviousGradleTemplateEnabled = previousValue.GradleTemplateEnabled, + GradlePropertiesTemplateEnabled = currentValue.GradlePropertiesTemplateEnabled, + PreviousGradlePropertiesTemplateEnabled = previousValue.GradlePropertiesTemplateEnabled, ProjectExportEnabled = currentValue.ProjectExportEnabled, PreviousProjectExportEnabled = previousValue.ProjectExportEnabled, }); @@ -1236,7 +1532,7 @@ private static void PollAndroidSdkRoot() { /// private static void ResolveOnAndroidSdkRootChange( object sender, AndroidSdkRootChangedArgs args) { - ScheduleResolve(true, null, true); + ScheduleResolve(true, false, null, true); } /// @@ -1244,12 +1540,23 @@ private static void ResolveOnAndroidSdkRootChange( /// /// Files or directories to delete. /// true if files are deleted, false otherwise. - private static bool DeleteFiles(IEnumerable filenames) - { + private static bool DeleteFiles(IEnumerable filenames) { if (filenames == null) return false; var failedToDelete = new List(); foreach (string artifact in filenames) { failedToDelete.AddRange(FileUtils.DeleteExistingFileOrDirectory(artifact)); + // Attempt to remove empty parent folders. + var folder = Path.GetDirectoryName(artifact); + while (!String.IsNullOrEmpty(folder) && + !Path.IsPathRooted(folder) && + String.Compare(folder, FileUtils.ASSETS_FOLDER) != 0) { + if (Directory.GetFiles(folder).Length != 0 || + Directory.GetDirectories(folder).Length != 0) { + break; + } + failedToDelete.AddRange(FileUtils.DeleteExistingFileOrDirectory(folder)); + folder = Path.GetDirectoryName(folder); + } } var deleteError = FileUtils.FormatError("Failed to delete files:", failedToDelete); if (!String.IsNullOrEmpty(deleteError)) { @@ -1304,7 +1611,7 @@ private static void ExecuteNextResolveJob() { public static void Resolve(Action resolutionComplete = null, bool forceResolution = false, Action resolutionCompleteWithResult = null) { - ScheduleResolve(forceResolution, + ScheduleResolve(forceResolution, false, (success) => { if (resolutionComplete != null) { resolutionComplete(); @@ -1335,11 +1642,13 @@ private static void PollManualResetEvent(ManualResetEvent eventToPoll) { /// Whether resolution should be executed when no dependencies /// have changed. This is useful if a dependency specifies a wildcard in the version /// expression. + /// Whether to unconditionally close the resolution + /// window when complete. /// true if successful, false otherwise. - public static bool ResolveSync(bool forceResolution) { + private static bool ResolveSync(bool forceResolution, bool closeWindowOnCompletion) { bool successful = false; var completeEvent = new ManualResetEvent(false); - ScheduleResolve(forceResolution, (success) => { + ScheduleResolve(forceResolution, closeWindowOnCompletion, (success) => { successful = success; completeEvent.Set(); }, false); @@ -1347,23 +1656,54 @@ public static bool ResolveSync(bool forceResolution) { return successful; } + /// + /// Resolve dependencies synchronously. + /// + /// Whether resolution should be executed when no dependencies + /// have changed. This is useful if a dependency specifies a wildcard in the version + /// expression. + /// true if successful, false otherwise. + public static bool ResolveSync(bool forceResolution) { + return ResolveSync(forceResolution, false); + } + + /// + /// Remove libraries references from Gradle template files and patch POM files to work + /// with the Gradle template. + /// + /// Whether to update mainTemplate.gradle. + /// Whether to update settingsTemplate.gradle. + /// have changed. This is useful if a dependency specifies a wildcard in the version + /// expression. + private static void DeleteResolvedLibrariesFromGradleTemplate( + bool updateMainTemplate, bool updateSettingsTemplate) { + LocalMavenRepository.PatchPomFilesInLocalRepos( + PlayServicesSupport.GetAllDependencies().Values); + if (updateMainTemplate && GradleTemplateEnabled) { + GradleTemplateResolver.InjectDependencies(new List()); + } + if (updateSettingsTemplate && GradleSettingsTemplateEnabled) { + string lastError; + GradleTemplateResolver.InjectSettings(new List(), out lastError); + } + } + /// /// Delete all resolved libraries asynchronously. /// /// Delegate called when delete is complete. public static void DeleteResolvedLibraries(System.Action complete = null) { RunOnMainThread.Schedule(() => { - if (Resolver.AutomaticResolutionEnabled()) { + if (AutomaticResolutionEnabled) { Log("Disabling auto-resolution to prevent libraries from being " + "resolved after deletion.", level: LogLevel.Warning); - GooglePlayServices.SettingsDialog.EnableAutoResolution = false; + SettingsDialogObj.EnableAutoResolution = false; } + PlayServicesResolver.analytics.Report("/deleteresolved", + "Delete Resolved Packages"); DeleteLabeledAssets(); - LocalMavenRepository.PatchPomFilesInLocalRepos( - PlayServicesSupport.GetAllDependencies().Values); - if (GradleTemplateEnabled) { - GradleTemplateResolver.InjectDependencies(new List()); - } + DeleteResolvedLibrariesFromGradleTemplate(updateMainTemplate: true, + updateSettingsTemplate: true); if (complete != null) complete(); }, 0); } @@ -1385,10 +1725,12 @@ public static void DeleteResolvedLibrariesSync() { /// Whether resolution should be executed when no dependencies /// have changed. This is useful if a dependency specifies a wildcard in the version /// expression. + /// Whether to unconditionally close the resolution + /// window when complete. /// Delegate called when resolution is complete /// with a parameter that indicates whether it succeeded or failed. /// Whether this is an auto-resolution job. - private static void ScheduleResolve(bool forceResolution, + private static void ScheduleResolve(bool forceResolution, bool closeWindowOnCompletion, Action resolutionCompleteWithResult, bool isAutoResolveJob) { bool firstJob; @@ -1406,43 +1748,137 @@ private static void ScheduleResolve(bool forceResolution, new ResolutionJob( isAutoResolveJob, () => { - ResolveUnsafe(resolutionComplete: (success) => { + ResolveUnsafeAfterMainTemplateCheck( + (success) => { SignalResolveJobComplete(() => { if (resolutionCompleteWithResult != null) { resolutionCompleteWithResult(success); } }); }, - forceResolution: forceResolution, - isAutoResolveJob: isAutoResolveJob); + forceResolution, + isAutoResolveJob, + closeWindowOnCompletion); })); } if (firstJob) ExecuteNextResolveJob(); } /// - /// Resolve dependencies. + /// Ensures that the mainTemplate.gradle and gradle.properties files are present in the project, + /// creating them via the Unity Editor's template files if needed. + /// + /// True if both files are present. + private static bool EnableGradleTemplates() { + return GradleTemplateResolver.EnsureGradleTemplateEnabled(GradleTemplateResolver.GradleTemplateFilename) && + GradleTemplateResolver.EnsureGradleTemplateEnabled(GradleTemplateResolver.GradlePropertiesTemplateFilename); + } + + /// + /// Resolve dependencies after checking if mainTemplate.gradle is enabled (or previously disabled). /// /// Delegate called when resolution is complete /// with a parameter that indicates whether it succeeded or failed. /// Whether resolution should be executed when no dependencies /// have changed. This is useful if a dependency specifies a wildcard in the version /// expression. - private static void ResolveUnsafe(Action resolutionComplete = null, - bool forceResolution = false, - bool isAutoResolveJob = false) { - JavaUtilities.CheckJdkForApiLevel(); + /// Whether this is an auto-resolution job. + /// Whether to unconditionally close the resolution + /// window when complete. + private static void ResolveUnsafeAfterMainTemplateCheck(Action resolutionComplete, + bool forceResolution, + bool isAutoResolveJob, + bool closeWindowOnCompletion) { + // If mainTemplate.gradle is already enabled, or if the user has rejected the switch, + // move to the next step. + if (GradleTemplateEnabled || + SettingsDialogObj.UserRejectedGradleUpgrade) { + ResolveUnsafeAfterJetifierCheck(resolutionComplete, forceResolution, isAutoResolveJob, closeWindowOnCompletion); + return; + } + + // Else, if there are no resolved files tracked by this (aka, it hasn't been run before), + // turn on mainTemplate, and log a message to the user. + // Or, if using Batch mode, we want to enable the templates as well, since that is now the + // desired default behavior. If Users want to preserve the old method, they can save their + // SettingsObject with the UserRejectedGradleUpgrade option enabled. + if (ExecutionEnvironment.InBatchMode || !PlayServicesResolver.FindLabeledAssets().Any()) { + EnableGradleTemplates(); + ResolveUnsafeAfterJetifierCheck(resolutionComplete, forceResolution, isAutoResolveJob, closeWindowOnCompletion); + return; + } + + // Else, prompt the user to turn it on for them. + DialogWindow.Display( + "Enable Android Gradle templates?", + "Android Resolver recommends using Gradle templates " + + "for managing Android dependencies. The old method of downloading " + + "the dependencies into Plugins/Android is no longer recommended.", + DialogWindow.Option.Selected0, "Enable", "Disable", + complete: (selectedOption) => { + switch (selectedOption) { + case DialogWindow.Option.Selected0: // Enable + EnableGradleTemplates(); + break; + case DialogWindow.Option.Selected1: // Disable + SettingsDialogObj.UserRejectedGradleUpgrade = true; + break; + } + + // Either way, proceed with the resolution. + ResolveUnsafeAfterJetifierCheck(resolutionComplete, forceResolution, isAutoResolveJob, closeWindowOnCompletion); + }); + } - DeleteFiles(Resolver.OnBuildSettings()); + /// + /// Resolve dependencies after checking the configuration is compatible with the Jetifier + /// settings. + /// + /// Delegate called when resolution is complete + /// with a parameter that indicates whether it succeeded or failed. + /// Whether resolution should be executed when no dependencies + /// have changed. This is useful if a dependency specifies a wildcard in the version + /// expression. + /// Whether this is an auto-resolution job. + /// Whether to unconditionally close the resolution + /// window when complete. + private static void ResolveUnsafeAfterJetifierCheck(Action resolutionComplete, + bool forceResolution, + bool isAutoResolveJob, + bool closeWindowOnCompletion) { + JavaUtilities.CheckJdkForApiLevel(); + CanEnableJetifierOrPromptUser( + "", + (jetifierEnabled) => { + ResolveUnsafeAfterPromptCheck(resolutionComplete, forceResolution, + isAutoResolveJob, closeWindowOnCompletion); + }); + } + /// + /// Resolve dependencies after checking whether the user should be prompted to perform + /// resolution now or turn off auto-resolution. + /// + /// Delegate called when resolution is complete + /// with a parameter that indicates whether it succeeded or failed. + /// Whether resolution should be executed when no dependencies + /// have changed. This is useful if a dependency specifies a wildcard in the version + /// expression. + /// Whether this is an auto-resolution job. + /// Whether to unconditionally close the resolution + /// window when complete. + private static void ResolveUnsafeAfterPromptCheck(Action resolutionComplete, + bool forceResolution, + bool isAutoResolveJob, + bool closeWindowOnCompletion) { // If the internal build system is being used and AAR explosion is disabled the build // is going to fail so warn and enable explosion. if (!AndroidBuildSystemSettings.Current.GradleBuildEnabled && - !GooglePlayServices.SettingsDialog.ExplodeAars) { + !SettingsDialogObj.ExplodeAars) { Log("AAR explosion *must* be enabled when the internal build " + "system is selected, otherwise the build will very likely fail. " + "Enabling the 'explode AARs' setting.", level: LogLevel.Warning); - GooglePlayServices.SettingsDialog.ExplodeAars = true; + SettingsDialogObj.ExplodeAars = true; } xmlDependencies.ReadAll(logger); @@ -1453,11 +1889,9 @@ private static void ResolveUnsafe(Action resolutionComplete = null, if (PlayServicesResolver.FindLabeledAssets() != null) { Log("Stale dependencies exist. Deleting assets...", level: LogLevel.Verbose); DeleteLabeledAssets(); - if (GradleTemplateEnabled) { - GradleTemplateResolver.InjectDependencies( - PlayServicesSupport.GetAllDependencies().Values); - } } + DeleteResolvedLibrariesFromGradleTemplate(updateMainTemplate: true, + updateSettingsTemplate: true); if (resolutionComplete != null) { resolutionComplete(true); @@ -1468,59 +1902,73 @@ private static void ResolveUnsafe(Action resolutionComplete = null, // If we are not in auto-resolution mode and not in batch mode // prompt the user to see if they want to resolve dependencies // now or later. - if (GooglePlayServices.SettingsDialog.PromptBeforeAutoResolution && + if (SettingsDialogObj.PromptBeforeAutoResolution && isAutoResolveJob && !ExecutionEnvironment.InBatchMode) { - bool shouldResolve = false; - AlertModal alert = new AlertModal { - Title = "Enable Android Auto-resolution?", - Message = "The Play Services Resolver has detected a change " + - " and would to resolve conflicts and download Android dependencies." + - "\n\n\"Disable Auto-Resolution\" will require manually " + - "running resolution using \"Assets > Play Services Resolver " + - "> Android Resolver > Resolve\" menu item. Failure to " + - "resolve Android dependencies will result " + - "in an non-functional application." + - "\n\nEnable auto-resolution again via " + - "\"Assets > Play Services Resolver " + - "> Android Resolver > Settings.", - Ok = new AlertModal.LabeledAction { - Label = "Enable", - DelegateAction = () => { - shouldResolve = true; - GooglePlayServices.SettingsDialog.PromptBeforeAutoResolution = false; + DialogWindow.Display( + "Enable Android Auto-resolution?", + "Android Resolver has detected a change " + + " and would to resolve conflicts and download Android dependencies." + + "\n\n\"Disable Auto-Resolution\" will require manually " + + "running resolution using \"Assets > External Dependency Manager " + + "> Android Resolver > Resolve\" menu item. Failure to " + + "resolve Android dependencies will result " + + "in an non-functional application." + + "\n\nEnable auto-resolution again via " + + "\"Assets > External Dependency Manager " + + "> Android Resolver > Settings.", + DialogWindow.Option.Selected0, "Enable", "Disable", + complete: (selectedOption) => { + bool shouldResolve = true; + switch (selectedOption) { + case DialogWindow.Option.Selected0: // Enable + shouldResolve = true; + SettingsDialogObj.PromptBeforeAutoResolution = false; + break; + case DialogWindow.Option.Selected1: // Disable + SettingsDialogObj.EnableAutoResolution = false; + SettingsDialogObj.PromptBeforeAutoResolution = false; + shouldResolve = false; + break; } - }, - Cancel = new AlertModal.LabeledAction { - Label = "Disable", - DelegateAction = () => { - GooglePlayServices.SettingsDialog.EnableAutoResolution = false; - GooglePlayServices.SettingsDialog.PromptBeforeAutoResolution = false; - shouldResolve = false; + if (shouldResolve) { + ResolveUnsafe(resolutionComplete, forceResolution, + closeWindowOnCompletion); + } else { + if (resolutionComplete != null) { + resolutionComplete(false); + } } - } - }; - - alert.Display(); - - if (!shouldResolve) { - if (resolutionComplete != null) { - resolutionComplete(false); - } - - return; - } + }); + } else { + ResolveUnsafe(resolutionComplete, forceResolution, closeWindowOnCompletion); } + } + /// + /// Resolve dependencies. + /// + /// Delegate called when resolution is complete + /// with a parameter that indicates whether it succeeded or failed. + /// Whether resolution should be executed when no dependencies + /// have changed. This is useful if a dependency specifies a wildcard in the version + /// expression. + /// Whether to unconditionally close the resolution + /// window when complete. + private static void ResolveUnsafe(Action resolutionComplete, + bool forceResolution, bool closeWindowOnCompletion) { if (forceResolution) { + Log("Forcing resolution...", level: LogLevel.Verbose); DeleteLabeledAssets(); } else { + Log("Checking for changes from previous resolution...", level: LogLevel.Verbose); // Only resolve if user specified dependencies changed or the output files // differ to what is present in the project. var currentState = DependencyState.GetState(); var previousState = DependencyState.ReadFromFile(); if (previousState != null) { if (currentState.Equals(previousState)) { + Log("No changes found, resolution skipped.", level: LogLevel.Verbose); if (resolutionComplete != null) resolutionComplete(true); return; } @@ -1534,41 +1982,123 @@ private static void ResolveUnsafe(Action resolutionComplete = null, // Delete all labeled assets to make sure we don't leave any stale transitive // dependencies in the project. DeleteLabeledAssets(); + } else { + Log("Failed to parse previous resolution state, running resolution...", + level: LogLevel.Verbose); } } - System.IO.Directory.CreateDirectory(GooglePlayServices.SettingsDialog.PackageDir); + var requestedDependencies = PlayServicesSupport.GetAllDependencies(); + System.IO.Directory.CreateDirectory(SettingsDialogObj.PackageDir); Log(String.Format("Resolving the following dependencies:\n{0}\n", String.Join("\n", (new List( - PlayServicesSupport.GetAllDependencies().Keys)).ToArray())), + requestedDependencies.Keys)).ToArray())), level: LogLevel.Verbose); + var resolutionParameters = GetResolutionMeasurementParameters(null); + PlayServicesResolver.analytics.Report("/resolve", resolutionParameters, "Resolve"); + // Writes the current dependency state and reports whether resolution was successful. Action finishResolution = (bool succeeded, string error) => { AssetDatabase.Refresh(); DependencyState.GetState().WriteToFile(); - Log(String.Format("Resolution {0}.\n\n{1}", - succeeded ? "Succeeded" : "Failed", - error), level: LogLevel.Verbose); + if (succeeded) { + PlayServicesResolver.analytics.Report("/resolve/success", resolutionParameters, + "Resolve Successful"); + Log(String.Format("Resolution Succeeded.\n\n{0}", error), + level: LogLevel.Verbose); + } else { + PlayServicesResolver.analytics.Report("/resolve/failed", resolutionParameters, + "Resolve Failed"); + Log(String.Format("Resolution Failed.\n\n{0}", error), + level: LogLevel.Error); + } if (resolutionComplete != null) { RunOnMainThread.Run(() => { resolutionComplete(succeeded); }); } }; - if (GradleTemplateEnabled) { + // If a gradle template is present but patching is disabled, remove managed libraries + // from the template. + DeleteResolvedLibrariesFromGradleTemplate( + updateMainTemplate: GradleTemplateEnabled && + !SettingsDialogObj.PatchMainTemplateGradle, + updateSettingsTemplate: GradleSettingsTemplateEnabled && + !SettingsDialogObj.PatchSettingsTemplateGradle); + + Func patchGradleProperties = () => { + // For Unity 2019.3 and above, warn the user if jetifier is enabled + // and custom gradle properties template is not enabled in build settings. + if (GradleBuildEnabled && + Google.VersionHandler.GetUnityVersionMajorMinor() >= 2019.3f && + SettingsDialogObj.UseJetifier && + SettingsDialogObj.PatchPropertiesTemplateGradle && + GradleTemplateEnabled && + !GradlePropertiesTemplateEnabled) { + lastError = String.Format( + "Resolution failed because EDM4U could not enable Jetifier " + + "in Unity {0} without Custom Gradle Properties Template. " + + "Please enable 'Custom Gradle Properties Template' found under " + + "'Player Settings > Settings for Android -> Publishing Settings' menu. " + + "Due to changes in Gradle project generated by Unity 2019.3 and "+ + "above, our recommended way of enabling Jetifier is by injecting "+ + "properties to Assets/Plugins/Android/gradleTemplate.properties.\n" + + "If you like to patch this yourself, simply disable 'Patch " + + "gradleTemplate.properties' in Android Resolver settings.", + Google.VersionHandler.GetUnityVersionMajorMinor() + ); + return false; + } + + // If there is a custom gradle properties template file and jetifier + // is enabled, inject the required properties in that file. + // This is the recommended way of doing things in Unity 2019.3 and above + // because the structure of gradle projects built by Unity has changed. + if (GradlePropertiesTemplateEnabled && + SettingsDialogObj.UseJetifier && + SettingsDialogObj.PatchPropertiesTemplateGradle) { + return GradleTemplateResolver.InjectProperties(); + } + return true; + }; + + Func, bool> patchSettingsTemplateGradle = (dependencies) => { + if (GradleBuildEnabled && GradleTemplateEnabled && + SettingsDialogObj.PatchSettingsTemplateGradle) { + return GradleTemplateResolver.InjectSettings(dependencies, out lastError); + } + return true; + }; + + if (GradleTemplateEnabled && + SettingsDialogObj.PatchMainTemplateGradle) { + lastError = ""; RunOnMainThread.Run(() => { - finishResolution(GradleTemplateResolver.InjectDependencies( - PlayServicesSupport.GetAllDependencies().Values), ""); + var dependencies = PlayServicesSupport.GetAllDependencies().Values; + finishResolution( + GradleTemplateResolver.InjectDependencies(dependencies) && + patchGradleProperties() && + patchSettingsTemplateGradle(dependencies), lastError); }); } else { lastError = ""; - Resolver.DoResolution( - svcSupport, GooglePlayServices.SettingsDialog.PackageDir, - () => { - RunOnMainThread.Run(() => { - finishResolution(String.IsNullOrEmpty(lastError), lastError); - }); + if (GradleResolverInstance != null) { + GradleResolverInstance.DoResolution( + SettingsDialogObj.PackageDir, + closeWindowOnCompletion, + () => { + RunOnMainThread.Run(() => { + finishResolution(String.IsNullOrEmpty(lastError) && + patchGradleProperties(), lastError); + }); + }); + } else { + // Fail the resolution if gradleResolver is not initialized. + RunOnMainThread.Run(() => { + finishResolution(false, "GradleResolver is not created. Is your " + + "current build target set to Android?"); }); + } } } @@ -1576,17 +2106,16 @@ private static void ResolveUnsafe(Action resolutionComplete = null, /// Display a dialog explaining that the resolver is disabled in the current configuration. /// private static void NotAvailableDialog() { - EditorUtility.DisplayDialog("Play Services Resolver.", - "Resolver not enabled. " + - "Android platform must be selected.", - "OK"); + DialogWindow.Display("Android Resolver.", + "Resolver not enabled. Android platform must be selected.", + DialogWindow.Option.Selected0, "OK"); } /// /// Link to the documentation. /// - [MenuItem("Assets/Play Services Resolver/Android Resolver/Documentation")] + [MenuItem("Assets/External Dependency Manager/Android Resolver/Documentation")] public static void OpenDocumentation() { Application.OpenURL(VersionHandlerImpl.DocumentationUrl("#android-resolver-usage")); } @@ -1594,41 +2123,38 @@ public static void OpenDocumentation() { /// /// Add a menu item for resolving the jars manually. /// - [MenuItem("Assets/Play Services Resolver/Android Resolver/Settings")] + [MenuItem("Assets/External Dependency Manager/Android Resolver/Settings")] public static void SettingsDialog() { - if (Resolver != null) - { - Resolver.ShowSettingsDialog(); - } - else - { - DefaultResolver.ShowSettings(); - } + SettingsDialog window = (SettingsDialog)EditorWindow.GetWindow( + typeof(SettingsDialog), true, "Android Resolver Settings"); + window.Initialize(); + window.Show(); } /// /// Interactive resolution of dependencies. /// private static void ExecuteMenuResolve(bool forceResolution) { - if (Resolver == null) { + if (GradleResolverInstance == null) { NotAvailableDialog(); return; } ScheduleResolve( - forceResolution, (success) => { - EditorUtility.DisplayDialog( + forceResolution, false, (success) => { + DialogWindow.Display( "Android Dependencies", String.Format("Resolution {0}", success ? "Succeeded" : "Failed!\n\nYour application will not run, see " + - "the log for details."), "OK"); + "the log for details."), + DialogWindow.Option.Selected0, "OK"); }, false); } /// /// Add a menu item for resolving the jars manually. /// - [MenuItem("Assets/Play Services Resolver/Android Resolver/Resolve")] + [MenuItem("Assets/External Dependency Manager/Android Resolver/Resolve")] public static void MenuResolve() { ExecuteMenuResolve(false); } @@ -1636,7 +2162,7 @@ public static void MenuResolve() { /// /// Add a menu item to force resolve the jars manually. /// - [MenuItem("Assets/Play Services Resolver/Android Resolver/Force Resolve")] + [MenuItem("Assets/External Dependency Manager/Android Resolver/Force Resolve")] public static void MenuForceResolve() { ExecuteMenuResolve(true); } @@ -1644,7 +2170,7 @@ public static void MenuForceResolve() { /// /// Add a menu item to clear all resolved libraries. /// - [MenuItem("Assets/Play Services Resolver/Android Resolver/Delete Resolved Libraries")] + [MenuItem("Assets/External Dependency Manager/Android Resolver/Delete Resolved Libraries")] public static void MenuDeleteResolvedLibraries() { DeleteResolvedLibrariesSync(); } @@ -1671,7 +2197,7 @@ private static IEnumerable GetOrReadDependencies( public static IList> GetPackageSpecs( IEnumerable dependencies = null) { return new List>(new SortedList( - ResolverVer1_1.DependenciesToPackageSpecs(GetOrReadDependencies(dependencies)))); + GradleResolver.DependenciesToPackageSpecs(GetOrReadDependencies(dependencies)))); } /// @@ -1680,7 +2206,7 @@ public static IList> GetPackageSpecs( /// List of repo, source pairs. public static IList> GetRepos( IEnumerable dependencies = null) { - return ResolverVer1_1.DependenciesToRepoUris(GetOrReadDependencies(dependencies)); + return GradleResolver.DependenciesToRepoUris(GetOrReadDependencies(dependencies)); } /// @@ -1690,20 +2216,15 @@ public static IList> GetRepos( internal static IList GradleMavenReposLines(ICollection dependencies) { var lines = new List(); if (dependencies.Count > 0) { - lines.Add("allprojects {"); - lines.Add(" repositories {"); - lines.Add(" maven {"); - lines.Add(" url \"https://maven.google.com/""); - lines.Add(" }"); - foreach (var repoAndSources in GetRepos(dependencies: dependencies)) { - lines.Add(" maven {"); - lines.Add(String.Format(" url \"{0}\" // {1}", repoAndSources.Key, - repoAndSources.Value)); - lines.Add(" }"); - } - lines.Add(" mavenLocal()"); - lines.Add(" jcenter()"); - lines.Add(" mavenCentral()"); + var projectPath = FileUtils.PosixPathSeparators(Path.GetFullPath(".")); + var projectFileUri = GradleResolver.RepoPathToUri(projectPath); + lines.Add("([rootProject] + (rootProject.subprojects as List)).each { project ->"); + lines.Add(" project.repositories {"); + lines.AddRange(GradleTemplateResolver.GradleMavenReposLinesFromDependencies( + dependencies: dependencies, + addMavenGoogle: true, + addMavenCentral: true, + addMavenLocal: true)); lines.Add(" }"); lines.Add("}"); } @@ -1721,31 +2242,153 @@ internal static IList GradleDependenciesLines( ICollection dependencies, bool includeDependenciesBlock = true) { var lines = new List(); if (dependencies.Count > 0) { + // Select the appropriate dependency include statement based upon the Gradle + // version. "implementation" was introduced in Gradle 3.4 that is used by the + // Android Gradle plugin 3.0.0 and newer: + // https://docs.gradle.org/3.4/release-notes.html#the-java-library-plugin + // https://developer.android.com/studio/releases/gradle-plugin#3-0-0 + var version = GradleVersion; + var versionComparer = new Dependency.VersionComparer(); + var includeStatement = + !String.IsNullOrEmpty(version) && + versionComparer.Compare("3.4", version) >= 0 ? + "implementation" : "compile"; if (includeDependenciesBlock) lines.Add("dependencies {"); + // Build a map of dependencies with the max version of that dependency. + // If different packages have different versions of the same dependency, + // we want to activate only the highest version but still include the + // other versions as commented out dependency lines. + var dependenciesMaxVersions = new Dictionary(); + // Maintain a set of packages which we want to comment out. + HashSet packageSpecStringsToComment = new HashSet(); + foreach( var dependency in dependencies) { + Dependency dependencyMaxVersion; + if (dependenciesMaxVersions.TryGetValue(dependency.VersionlessKey, out dependencyMaxVersion)){ + if(versionComparer.Compare(dependency.Version, dependencyMaxVersion.Version) < 0) { + dependenciesMaxVersions[dependency.VersionlessKey] = dependency; + // We found a new larger version. Comment out older one + // Build a single item list since `GetPackageSpecs` + // accepts an IEnumerable type. + var packageSpecString = GradleResolver.DependencyToPackageSpec(dependencyMaxVersion); + packageSpecStringsToComment.Add(packageSpecString); + } + else { + // Dependency version is smaller than current max. + var packageSpecString = GradleResolver.DependencyToPackageSpec(dependency); + packageSpecStringsToComment.Add(packageSpecString); + } + } + else { + // First time encountering this dependency. + dependenciesMaxVersions[dependency.VersionlessKey] = dependency; + } + } foreach (var packageSpecAndSources in GetPackageSpecs(dependencies: dependencies)) { - lines.Add(String.Format( - " compile '{0}' // {1}", - packageSpecAndSources.Key, packageSpecAndSources.Value)); + string line = " "; + if (packageSpecStringsToComment.Contains(packageSpecAndSources.Key)) { + PlayServicesResolver.Log( + String.Format( + "Ignoring duplicate package {0} with older version.", + packageSpecAndSources.Key), + level: LogLevel.Info + ); + line += "// "; + } + line += String.Format( + "{0} '{1}' // {2}", includeStatement, packageSpecAndSources.Key, + packageSpecAndSources.Value); + lines.Add(line); } if (includeDependenciesBlock) lines.Add("}"); } return lines; } + // Extracts the ABI from a native library path in an AAR. + // In an AAR, native libraries are under the jni directory, in an APK they're placed under + // the lib directory. + private static Regex nativeLibraryPath = new Regex(@"^jni/([^/]+)/.*\.so$"); + + /// + /// Get the Android packaging options as lines that can be included in a Gradle file. + /// + internal static IList PackagingOptionsLines(ICollection dependencies) { + var lines = new List(); + if (dependencies.Count > 0) { + var currentAbis = AndroidAbis.Current.ToSet(); + var excludeFiles = new HashSet(); + // Support for wildcard based excludes were added in Android Gradle plugin 3.0.0 + // which requires Gradle 4.1+. Also, version 2.3.0 was locked to Gradle 2.1.+ so + // it's possible to infer the Android Gradle plugin from the Gradle version. + var version = GradleVersion; + var wildcardExcludesSupported = + !String.IsNullOrEmpty(version) && + (new Dependency.VersionComparer()).Compare("4.1", version) >= 0; + if (wildcardExcludesSupported) { + var allAbis = new HashSet(AndroidAbis.AllSupported); + allAbis.ExceptWith(currentAbis); + foreach (var abi in allAbis) { + excludeFiles.Add(String.Format("/lib/{0}/**", abi)); + } + } else { + // Android Gradle plugin 2.x only supported exclusion of packaged files using + // APK relative paths. + foreach (var aar in LocalMavenRepository.FindAarsInLocalRepos(dependencies)) { + foreach (var path in ListZip(aar)) { + var posixPath = FileUtils.PosixPathSeparators(path); + var match = nativeLibraryPath.Match(posixPath); + if (match != null && match.Success) { + var abi = match.Result("$1"); + if (!currentAbis.Contains(abi)) { + excludeFiles.Add( + String.Format( + "lib/{0}", posixPath.Substring("jni/".Length))); + } + } + } + } + } + if (excludeFiles.Count > 0) { + var sortedExcludeFiles = new List(excludeFiles); + sortedExcludeFiles.Sort(); + lines.Add("android {"); + + // `packagingOptions` is replaced by `packaging` keyword in Android Gradle plugin 8.0+ + if ((new Dependency.VersionComparer()).Compare("8.0", AndroidGradlePluginVersion) >= 0) { + lines.Add(" packaging {"); + } else { + lines.Add(" packagingOptions {"); + } + foreach (var filename in sortedExcludeFiles) { + // Unity's Android extension replaces ** in the template with an empty + // string presumably due to the token expansion it performs. It's not + // possible to escape the expansion so we workaround it by concatenating + // strings. + lines.Add(String.Format(" exclude ('{0}')", + filename.Replace("**", "*' + '*"))); + } + lines.Add(" }"); + lines.Add("}"); + } + } + return lines; + } + /// /// Display the set of dependncies / libraries currently included in the project. /// This prints out the set of libraries in a form that can be easily included in a Gradle /// script. This does not resolve dependency conflicts, it simply displays what is included /// by plugins in the project. /// - [MenuItem("Assets/Play Services Resolver/Android Resolver/Display Libraries")] + [MenuItem("Assets/External Dependency Manager/Android Resolver/Display Libraries")] public static void MenuDisplayLibraries() { xmlDependencies.ReadAll(logger); var dependencies = PlayServicesSupport.GetAllDependencies().Values; var lines = new List(); - lines.AddRange(GradleMavenReposLines(dependencies: dependencies)); - lines.AddRange(GradleDependenciesLines(dependencies: dependencies)); + lines.AddRange(GradleMavenReposLines(dependencies)); + lines.AddRange(GradleDependenciesLines(dependencies)); + lines.AddRange(PackagingOptionsLines(dependencies)); var dependenciesString = String.Join("\n", lines.ToArray()); Log(dependenciesString); var window = TextAreaDialog.CreateTextAreaDialog("Android Libraries"); @@ -1753,26 +2396,6 @@ public static void MenuDisplayLibraries() { window.Show(); } - /// - /// Called when settings change. - /// - internal static void OnSettingsChanged() { - if (previousInstallAndroidPackages != - GooglePlayServices.SettingsDialog.InstallAndroidPackages) { - DeleteLabeledAssets(); - } - previousInstallAndroidPackages = - GooglePlayServices.SettingsDialog.InstallAndroidPackages; - PlayServicesSupport.verboseLogging = - GooglePlayServices.SettingsDialog.VerboseLogging || - ExecutionEnvironment.InBatchMode; - logger.Verbose = GooglePlayServices.SettingsDialog.VerboseLogging; - if (Resolver != null) { - PatchAndroidManifest(GetAndroidApplicationId(), null); - ScheduleAutoResolve(delayInMilliseconds: 0); - } - } - /// /// Label a set of assets that should be managed by this plugin. /// @@ -1799,20 +2422,8 @@ internal static void LabelAssets(IEnumerable assetPaths, int totalAssets = assetsToProcess.Count; RunOnMainThread.PollOnUpdateUntilComplete(() => { var remainingAssets = assetsToProcess.Count; - // Display summary of processing and call the completion function. - if (remainingAssets == 0) { - if (assetsWithoutAssetImporter.Count > 0 && displayWarning) { - Log(String.Format( - "Failed to add tracking label {0} to some assets.\n\n" + - "The following files will not be managed by this module:\n" + - "{1}\n", ManagedAssetLabel, - String.Join( - "\n", new List(assetsWithoutAssetImporter).ToArray())), - level: LogLevel.Warning); - } - if (complete != null) complete(assetsWithoutAssetImporter); - return true; - } + // Processing is already complete. + if (remainingAssets == 0) return true; var assetPath = assetsToProcess[0]; assetsToProcess.RemoveAt(0); // Ignore asset meta files which are used to store the labels and files that @@ -1856,17 +2467,93 @@ internal static void LabelAssets(IEnumerable assetPaths, } else { assetsWithoutAssetImporter.Add(assetPath); } + + // Display summary of processing and call the completion function. + if (assetsToProcess.Count == 0) { + if (assetsWithoutAssetImporter.Count > 0 && displayWarning) { + Log(String.Format( + "Failed to add tracking label {0} to some assets.\n\n" + + "The following files will not be managed by this module:\n" + + "{1}\n", ManagedAssetLabel, + String.Join( + "\n", new List(assetsWithoutAssetImporter).ToArray())), + level: LogLevel.Warning); + } + if (complete != null) complete(assetsWithoutAssetImporter); + return true; + } return false; }, synchronous: synchronous); } + /// + /// Copy asset to target location and apply labels to be managed by this plugin. + /// + /// Location to copy from. + /// Location to copy to. + /// If true, this will attempt to delete existing file at target + /// location. If false, this will return error if target file exists. + /// Error message. Empty if no error occurs. + internal static string CopyAssetAndLabel(string sourceLocation, string targetLocation, + bool force = false) { + if (String.Compare(sourceLocation, targetLocation) == 0) { + return ""; + } + if (!File.Exists(sourceLocation)) { + return String.Format("Source file {0} not found.", + sourceLocation); + } + if (File.Exists(targetLocation)) { + if (force) { + if (FileUtils.DeleteExistingFileOrDirectory(targetLocation, true).Count != 0) { + return String.Format( + "Failed to remove existing target file {0}.", + targetLocation); + } + } else { + return String.Format("Target file {0} exists.", + targetLocation); + } + } + + var targetDir = Path.GetDirectoryName(targetLocation); + if (!FileUtils.CreateFolder(targetDir, logger)) { + return String.Format("Failed to create folders at {0}", targetDir); + } + + PlayServicesResolver.Log(String.Format("Copying {0} to {1}", + sourceLocation, targetLocation), + level: LogLevel.Verbose); + + // Use File.Copy() instead of AssetDatabase.CopyAsset() to prevent copying meta data. + try { + File.Copy(sourceLocation, targetLocation); + } catch (Exception e) { + return String.Format("Failed to copy {0} to {1} due to {2}", + sourceLocation, targetLocation, e.ToString()); + } + + var unlabeledAssets = new HashSet(); + LabelAssets( + new [] { targetLocation }, + complete: (unlabeled) => { unlabeledAssets.UnionWith(unlabeled); }); + if (unlabeledAssets.Count != 0) { + return String.Format("Cannot label {0} properly.", targetLocation); + } + + if (!File.Exists(targetLocation)) { + return String.Format("Cannot find the file at target location {0} after copy.", + targetLocation); + } + + return ""; + } + /// /// Find the set of assets managed by this plugin. /// internal static IEnumerable FindLabeledAssets() { - foreach (string assetGuid in AssetDatabase.FindAssets("l:" + ManagedAssetLabel)) { - yield return AssetDatabase.GUIDToAssetPath(assetGuid); - } + return VersionHandlerImpl.SearchAssetDatabase("l:" + ManagedAssetLabel); } /// @@ -1878,20 +2565,338 @@ internal static void DeleteLabeledAssets() { DeleteFiles(PlayServicesResolver.FindLabeledAssets()); } + /// + /// Called when settings change. + /// + internal static void OnSettingsChanged() { + PlayServicesSupport.verboseLogging = SettingsDialogObj.VerboseLogging; + logger.Verbose = SettingsDialogObj.VerboseLogging; + if (GradleResolverInstance != null) { + PatchAndroidManifest(GetAndroidApplicationId(), null); + Reresolve(); + } + } + + /// + /// Retrieves the current value of settings that should cause resolution if they change. + /// + /// Map of name to value for settings that affect resolution. + internal static Dictionary GetResolutionSettings() { + var buildSystemSettings = AndroidBuildSystemSettings.Current; + var androidAbis = AndroidAbis.Current; + return new Dictionary { + {"installAndroidPackages", SettingsDialogObj.InstallAndroidPackages.ToString()}, + {"packageDir", SettingsDialogObj.PackageDir.ToString()}, + {"explodeAars", SettingsDialogObj.ExplodeAars.ToString()}, + {"patchAndroidManifest", SettingsDialogObj.PatchAndroidManifest.ToString()}, + {"patchMainTemplateGradle", SettingsDialogObj.PatchMainTemplateGradle.ToString()}, + {"useFullCustomMavenRepoPathWhenExport", SettingsDialogObj.UseFullCustomMavenRepoPathWhenExport.ToString()}, + {"useFullCustomMavenRepoPathWhenNotExport", SettingsDialogObj.UseFullCustomMavenRepoPathWhenNotExport.ToString()}, + {"localMavenRepoDir", SettingsDialogObj.LocalMavenRepoDir.ToString()}, + {"useJetifier", SettingsDialogObj.UseJetifier.ToString()}, + {"bundleId", GetAndroidApplicationId()}, + {"gradleBuildEnabled", buildSystemSettings.GradleBuildEnabled.ToString()}, + {"gradleTemplateEnabled", buildSystemSettings.GradleTemplateEnabled.ToString()}, + {"gradlePropertiesTemplateEnabled", buildSystemSettings.GradlePropertiesTemplateEnabled.ToString()}, + {"projectExportEnabled", buildSystemSettings.ProjectExportEnabled.ToString()}, + {"androidAbis", androidAbis.ToString()}, + }; + } + + // List of resolution settings to report. + private static HashSet resolutionSettingMeasurementList = new HashSet() { + "installAndroidPackages", + "explodeAars", + "patchAndroidManifest", + "patchMainTemplateGradle", + "useFullCustomMavenRepoPathWhenExport", + "useFullCustomMavenRepoPathWhenNotExport", + "localMavenRepoDir", + "useJetifier", + "gradleBuildEnabled", + "gradleTemplateEnabled", + "projectExportEnabled", + "androidAbis", + }; + + /// + /// Get filtered resolution settings to report as parameters with measurement events. + /// + /// List of packages that were requested but not successfully + /// resolved or null if this shouldn't be reported. + /// List of KeyValuePair instances. + internal static ICollection> + GetResolutionMeasurementParameters(ICollection missingPackages) { + var parameters = new List>(); + parameters.Add(new KeyValuePair( + "autoResolution", SettingsDialogObj.EnableAutoResolution.ToString())); + parameters.Add(new KeyValuePair( + "useGradleDaemon", SettingsDialogObj.UseGradleDaemon.ToString())); + foreach (var setting in GetResolutionSettings()) { + if (resolutionSettingMeasurementList.Contains(setting.Key)) parameters.Add(setting); + } + var gradleVersion = GradleVersion; + if (!String.IsNullOrEmpty(gradleVersion)) { + parameters.Add(new KeyValuePair("gradleVersion", gradleVersion)); + } + var androidGradlePluginVersion = AndroidGradlePluginVersion; + if (!String.IsNullOrEmpty(androidGradlePluginVersion)) { + parameters.Add(new KeyValuePair("androidGradlePluginVersion", + androidGradlePluginVersion)); + } + parameters.Add( + new KeyValuePair( + "numRequestedPackages", + PlayServicesSupport.GetAllDependencies().Count.ToString())); + if (missingPackages != null) { + parameters.Add(new KeyValuePair("numMissingPackages", + missingPackages.Count.ToString())); + } + return parameters; + } + + // Matches Jetpack (AndroidX) library filenames. + private static Regex androidXLibrary = new Regex(@"^androidx\..*\.(jar|aar)$"); + + /// + /// Determine whether a list of files contains Jetpack libraries. + /// + /// true if any files are androidx libraries, false otherwise. + internal static bool FilesContainJetpackLibraries(IEnumerable filenames) { + foreach (var path in filenames) { + var match = androidXLibrary.Match(Path.GetFileName(path)); + if (match != null && match.Success) return true; + } + return false; + } + + /// + /// Prompt the user to change Unity's build settings, if the Jetifier is enabled but not + /// supported by the version of Gradle in use. + /// + /// Whether the user wants to use the jetifier. + /// Prefix added to dialogs shown by this method. + /// Called with true if the Jetifier is enabled, false + /// otherwise. + private static void CheckGradleVersionForJetifier(bool useJetifier, + string titlePrefix, + Action complete) { + // Minimum Android Gradle Plugin required to use the Jetifier. + const string MinimumAndroidGradlePluginVersionForJetifier = "3.2.0"; + bool displayedEnableJetifierDialog = false; + if (useJetifier && GradleTemplateEnabled && SettingsDialogObj.PatchMainTemplateGradle) { + var version = AndroidGradlePluginVersion; + if ((new Dependency.VersionComparer()).Compare( + MinimumAndroidGradlePluginVersionForJetifier, version) < 0) { + displayedEnableJetifierDialog = true; + DialogWindow.Display( + titlePrefix + "Enable Jetifier?", + String.Format( + "Jetifier for Jetpack (AndroidX) libraries is only " + + "available with Android Gradle Plugin (AGP) version {0}. " + + "This Unity installation uses version {1} which does not include the " + + "Jetifier and therefore will not apply transformations to change " + + "all legacy Android Support Library references to use Jetpack " + + "(AndroidX).\n\n" + + "It's possible to use the Jetifier on Android Resolver managed " + + "dependencies by disabling mainTemplate.gradle patching.", + MinimumAndroidGradlePluginVersionForJetifier, version), + DialogWindow.Option.Selected1, "Disable Jetifier", "Ignore", + "Disable mainTemplate.gradle patching", + complete: (selectedOption) => { + switch (selectedOption) { + case DialogWindow.Option.Selected0: // Disable Jetifier + useJetifier = false; + break; + case DialogWindow.Option.Selected1: // Ignore + break; + case DialogWindow.Option.Selected2: + // Disable mainTemplate.gradle patching + SettingsDialogObj.PatchMainTemplateGradle = false; + break; + } + complete(useJetifier); + }); + } + } + if (!displayedEnableJetifierDialog) { + complete(useJetifier); + } + } + + /// + /// Result of CheckApiLevelForJetifier. + /// + private enum ApiLevelJetifierResult { + EnableAndSave, // Use the jetifier and save the setting. + EnableAndDoNotSave, // Use the jetifier, but do *not* save the setting. + DisableAndSave, // Do not use the jetifier and save the setting. + }; + + /// + /// Prompt the user to change Unity's build settings, if the Jetifier is enabled but not + /// supported by the selected API version. + /// + /// Whether the user wants to use the jetifier. + /// Prefix added to dialogs shown by this method. + /// Called with ApiLevelJetifierResult. + private static void CheckApiLevelForJetifier(bool useJetifier, + string titlePrefix, + Action complete) { + // Minimum target Android API level required to use Jetpack / AndroidX. + const int MinimumApiLevelForJetpack = 28; + int apiLevel = UnityCompat.GetAndroidTargetSDKVersion(); + if (useJetifier && apiLevel < MinimumApiLevelForJetpack) { + DialogWindow.Display( + titlePrefix + "Enable Jetpack?", + String.Format( + "Jetpack (AndroidX) libraries are only supported when targeting Android " + + "API {0} and above. The currently selected target API level is {1}.\n\n" + + "Would you like to set the project's target API level to {0}?", + MinimumApiLevelForJetpack, + apiLevel > 0 ? apiLevel.ToString() : "auto (max. installed)"), + DialogWindow.Option.Selected1, "Yes", "No", "Disable Jetifier", + complete: (selectedOption) => { + switch (selectedOption) { + case DialogWindow.Option.Selected0: // Yes + bool setSdkVersion = UnityCompat.SetAndroidTargetSDKVersion( + MinimumApiLevelForJetpack); + if (!setSdkVersion) { + // Get the highest installed SDK version to see whether it's + // suitable. + if (UnityCompat.FindNewestInstalledAndroidSDKVersion() >= + MinimumApiLevelForJetpack) { + // Set the mode to "auto" to use the latest installed + // version. + setSdkVersion = UnityCompat.SetAndroidTargetSDKVersion(-1); + } + } + if (!setSdkVersion) { + PlayServicesResolver.Log( + String.Format( + "Failed to set the Android Target API level to {0}, " + + "disabled the Jetifier.", MinimumApiLevelForJetpack), + level: LogLevel.Error); + useJetifier = false; + } + complete(useJetifier ? ApiLevelJetifierResult.EnableAndSave : + ApiLevelJetifierResult.DisableAndSave); + break; + case DialogWindow.Option.Selected1: // No + // Don't change the settings but report that AndroidX will not work. + complete(ApiLevelJetifierResult.EnableAndDoNotSave); + break; + case DialogWindow.Option.Selected2: // Disable Jetifier + complete(ApiLevelJetifierResult.DisableAndSave); + break; + } + }); + } else { + complete(ApiLevelJetifierResult.EnableAndSave); + } + } + + /// + /// Prompt the user to change Unity's build settings, if the Jetifier is enabled but not + /// supported with Unity's current build settings. + /// + /// Prefix added to dialogs shown by this method. + /// Called with true if the Jetifier is enabled, false + /// otherwise. + internal static void CanEnableJetifierOrPromptUser(string titlePrefix, + Action complete) { + bool useJetifier = SettingsDialogObj.UseJetifier; + if (!useJetifier || ExecutionEnvironment.InBatchMode) { + complete(useJetifier); + return; + } + + CheckGradleVersionForJetifier( + useJetifier, titlePrefix, (gradleVersionOkForJetifier) => { + CheckApiLevelForJetifier(gradleVersionOkForJetifier, titlePrefix, + (result) => { + switch (result) { + case ApiLevelJetifierResult.EnableAndSave: + useJetifier = true; + SettingsDialogObj.UseJetifier = true; + break; + case ApiLevelJetifierResult.EnableAndDoNotSave: + useJetifier = true; + break; + case ApiLevelJetifierResult.DisableAndSave: + useJetifier = false; + SettingsDialogObj.UseJetifier = false; + break; + } + complete(useJetifier); + }); + }); + } + + /// + /// List the contents of a zip file. + /// + /// Name of the zip file to query. + /// List of zip file contents. + internal static IEnumerable ListZip(string zipFile) { + var contents = new List(); + try { + string zipPath = Path.GetFullPath(zipFile); + Log(String.Format("Listing {0}", zipFile), level: LogLevel.Verbose); + CommandLine.Result result = CommandLine.Run( + JavaUtilities.JarBinaryPath, String.Format(" tf \"{0}\"", zipPath)); + if (result.exitCode == 0) { + foreach (var path in CommandLine.SplitLines(result.stdout)) { + contents.Add(FileUtils.PosixPathSeparators(path)); + } + } else { + Log(String.Format("Error listing \"{0}\"\n" + + "{1}", zipPath, result.message), level: LogLevel.Error); + } + } + catch (Exception e) { + Log(String.Format("Failed with exception {0}", e.ToString())); + throw e; + } + return contents; + } + /// /// Extract a zip file to the specified directory. /// /// Name of the zip file to extract. - /// List of files to extract from the archive. If this array - /// is empty or null all files are extracted. + /// Enumerable of files to extract from the archive. If this + /// array is empty or null all files are extracted. /// Directory to extract the zip file to. + /// If true, this will only extract the zip file if the target paths + /// are older than the source paths. If this is false or no extractFilenames are specified + /// this method will always extract files from the zip file overwriting the target + /// files. /// true if successful, false otherwise. - internal static bool ExtractZip(string zipFile, string[] extractFilenames, - string outputDirectory) { + internal static bool ExtractZip(string zipFile, IEnumerable extractFilenames, + string outputDirectory, bool update) { try { string zipPath = Path.GetFullPath(zipFile); - string extractFilesArg = extractFilenames != null && extractFilenames.Length > 0 ? - String.Format("\"{0}\"", String.Join("\" \"", extractFilenames)) : ""; + var zipFileModificationTime = File.GetLastWriteTime(zipPath); + string extractFilesArg = ""; + if (extractFilenames != null) { + bool outOfDate = !update; + if (update) { + foreach (var filename in extractFilenames) { + var path = Path.Combine(outputDirectory, filename); + if (!File.Exists(path) || + zipFileModificationTime.CompareTo( + File.GetLastWriteTime(path)) > 0) { + outOfDate = true; + break; + } + } + } + // If everything is up to date there is nothing to do. + if (!outOfDate) return true; + extractFilesArg = String.Format("\"{0}\"", String.Join("\" \"", + (new List(extractFilenames)).ToArray())); + } Log(String.Format("Extracting {0} ({1}) to {2}", zipFile, extractFilesArg, outputDirectory), level: LogLevel.Verbose); CommandLine.Result result = CommandLine.Run( diff --git a/source/AndroidResolver/src/SettingsDialog.cs b/source/AndroidResolver/src/SettingsDialog.cs new file mode 100644 index 00000000..33bea57d --- /dev/null +++ b/source/AndroidResolver/src/SettingsDialog.cs @@ -0,0 +1,732 @@ +// +// Copyright (C) 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace GooglePlayServices { + using System; + using System.Collections.Generic; + using System.IO; + using UnityEditor; + using UnityEngine; + using Google; + + /// + /// Settings dialog for PlayServices Resolver. + /// + public class SettingsDialog : EditorWindow { + /// + /// Loads / saves settings for this dialog. + /// + private class Settings { + internal bool enableAutoResolution; + internal bool autoResolveOnBuild; + internal bool useGradleDaemon; + internal bool installAndroidPackages; + internal string packageDir; + internal bool explodeAars; + internal bool patchAndroidManifest; + internal bool patchMainTemplateGradle; + internal bool patchPropertiesTemplateGradle; + internal bool patchSettingsTemplateGradle; + internal bool useFullCustomMavenRepoPathWhenExport; + internal bool useFullCustomMavenRepoPathWhenNotExport; + internal string localMavenRepoDir; + internal bool useJetifier; + internal bool verboseLogging; + internal bool autoResolutionDisabledWarning; + internal bool promptBeforeAutoResolution; + internal bool useProjectSettings; + internal bool userRejectedGradleUpgrade; + internal EditorMeasurement.Settings analyticsSettings; + + /// + /// Load settings into the dialog. + /// + internal Settings() { + enableAutoResolution = SettingsDialog.EnableAutoResolution; + autoResolveOnBuild = SettingsDialog.AutoResolveOnBuild; + useGradleDaemon = SettingsDialog.UseGradleDaemon; + installAndroidPackages = SettingsDialog.InstallAndroidPackages; + packageDir = SettingsDialog.PackageDir; + explodeAars = SettingsDialog.ExplodeAars; + patchAndroidManifest = SettingsDialog.PatchAndroidManifest; + patchMainTemplateGradle = SettingsDialog.PatchMainTemplateGradle; + patchPropertiesTemplateGradle = SettingsDialog.PatchPropertiesTemplateGradle; + patchSettingsTemplateGradle = SettingsDialog.PatchSettingsTemplateGradle; + useFullCustomMavenRepoPathWhenExport = SettingsDialog.UseFullCustomMavenRepoPathWhenExport; + useFullCustomMavenRepoPathWhenNotExport = SettingsDialog.UseFullCustomMavenRepoPathWhenNotExport; + localMavenRepoDir = SettingsDialog.LocalMavenRepoDir; + useJetifier = SettingsDialog.UseJetifier; + verboseLogging = SettingsDialog.VerboseLogging; + autoResolutionDisabledWarning = SettingsDialog.AutoResolutionDisabledWarning; + promptBeforeAutoResolution = SettingsDialog.PromptBeforeAutoResolution; + useProjectSettings = SettingsDialog.UseProjectSettings; + userRejectedGradleUpgrade = SettingsDialog.UserRejectedGradleUpgrade; + analyticsSettings = new EditorMeasurement.Settings(PlayServicesResolver.analytics); + } + + /// + /// Save dialog settings to preferences. + /// + internal void Save() { + SettingsDialog.UseGradleDaemon = useGradleDaemon; + SettingsDialog.EnableAutoResolution = enableAutoResolution; + SettingsDialog.AutoResolveOnBuild = autoResolveOnBuild; + SettingsDialog.InstallAndroidPackages = installAndroidPackages; + if (SettingsDialog.ConfigurablePackageDir) SettingsDialog.PackageDir = packageDir; + SettingsDialog.ExplodeAars = explodeAars; + SettingsDialog.PatchAndroidManifest = patchAndroidManifest; + SettingsDialog.PatchMainTemplateGradle = patchMainTemplateGradle; + SettingsDialog.PatchPropertiesTemplateGradle = patchPropertiesTemplateGradle; + SettingsDialog.PatchSettingsTemplateGradle = patchSettingsTemplateGradle; + SettingsDialog.UseFullCustomMavenRepoPathWhenExport = useFullCustomMavenRepoPathWhenExport; + SettingsDialog.UseFullCustomMavenRepoPathWhenNotExport = useFullCustomMavenRepoPathWhenNotExport; + SettingsDialog.LocalMavenRepoDir = localMavenRepoDir; + SettingsDialog.UseJetifier = useJetifier; + SettingsDialog.VerboseLogging = verboseLogging; + SettingsDialog.AutoResolutionDisabledWarning = autoResolutionDisabledWarning; + SettingsDialog.PromptBeforeAutoResolution = promptBeforeAutoResolution; + SettingsDialog.UseProjectSettings = useProjectSettings; + SettingsDialog.UserRejectedGradleUpgrade = userRejectedGradleUpgrade; + analyticsSettings.Save(); + } + } + + const string Namespace = "GooglePlayServices."; + private const string AutoResolveKey = Namespace + "AutoResolverEnabled"; + private const string AutoResolveOnBuildKey = Namespace + "AutoResolveOnBuild"; + private const string PackageInstallKey = Namespace + "AndroidPackageInstallationEnabled"; + private const string PackageDirKey = Namespace + "PackageDirectory"; + private const string ExplodeAarsKey = Namespace + "ExplodeAars"; + private const string PatchAndroidManifestKey = Namespace + "PatchAndroidManifest"; + private const string PatchMainTemplateGradleKey = Namespace + "PatchMainTemplateGradle"; + private const string PatchPropertiesTemplateGradleKey = Namespace + "PatchPropertiesTemplateGradle"; + private const string PatchSettingsTemplateGradleKey = Namespace + "PatchSettingsTemplateGradle"; + private const string UseFullCustomMavenRepoPathWhenExportKey = Namespace + "UseFullCustomMavenRepoPathWhenExport"; + private const string UseFullCustomMavenRepoPathWhenNotExportKey = Namespace + "UseFullCustomMavenRepoPathWhenNotExport"; + private const string LocalMavenRepoDirKey = Namespace + "LocalMavenRepoDir"; + private const string UseJetifierKey = Namespace + "UseJetifier"; + private const string VerboseLoggingKey = Namespace + "VerboseLogging"; + private const string AutoResolutionDisabledWarningKey = + Namespace + "AutoResolutionDisabledWarning"; + private const string PromptBeforeAutoResolutionKey = + Namespace + "PromptBeforeAutoResolution"; + private const string UseGradleDaemonKey = Namespace + "UseGradleDaemon"; + private const string UserRejectedGradleUpgradeKey = Namespace + "UserRejectedGradleUpgrade"; + + // List of preference keys, used to restore default settings. + private static string[] PreferenceKeys = new[] { + AutoResolveKey, + AutoResolveOnBuildKey, + PackageInstallKey, + PackageDirKey, + ExplodeAarsKey, + PatchAndroidManifestKey, + PatchMainTemplateGradleKey, + PatchPropertiesTemplateGradleKey, + PatchSettingsTemplateGradleKey, + UseFullCustomMavenRepoPathWhenExportKey, + UseFullCustomMavenRepoPathWhenNotExportKey, + LocalMavenRepoDirKey, + UseJetifierKey, + VerboseLoggingKey, + AutoResolutionDisabledWarningKey, + PromptBeforeAutoResolutionKey, + UseGradleDaemonKey, + UserRejectedGradleUpgradeKey + }; + + internal const string AndroidPluginsDir = "Assets/Plugins/Android"; + + // Unfortunately, Unity currently does not search recursively search subdirectories of + // AndroidPluginsDir for Android library plugins. When this is supported - or we come up + // with a workaround - this can be enabled. + static bool ConfigurablePackageDir = false; + static string DefaultPackageDir = AndroidPluginsDir; + static string DefaultLocalMavenRepoDir = "Assets/GeneratedLocalRepo"; + + private Settings settings; + + internal static ProjectSettings projectSettings = new ProjectSettings(Namespace); + + // Previously validated package directory. + private static string previouslyValidatedPackageDir; + + private Vector2 scrollPosition = new Vector2(0, 0); + + /// + /// Reset settings of this plugin to default values. + /// + internal static void RestoreDefaultSettings() { + projectSettings.DeleteKeys(PreferenceKeys); + PlayServicesResolver.analytics.RestoreDefaultSettings(); + } + + internal static bool EnableAutoResolution { + set { + projectSettings.SetBool(AutoResolveKey, value); + if (value) { + PlayServicesResolver.LinkAutoResolution(); + } else { + PlayServicesResolver.UnlinkAutoResolution(); + } + } + get { return projectSettings.GetBool(AutoResolveKey, true); } + } + + internal static bool AutoResolveOnBuild { + set { + projectSettings.SetBool(AutoResolveOnBuildKey, value); + } + get { return projectSettings.GetBool(AutoResolveOnBuildKey, true); } + } + + internal static bool UseGradleDaemon { + private set { projectSettings.SetBool(UseGradleDaemonKey, value); } + get { return projectSettings.GetBool(UseGradleDaemonKey, false); } + } + + internal static bool InstallAndroidPackages { + private set { projectSettings.SetBool(PackageInstallKey, value); } + get { return projectSettings.GetBool(PackageInstallKey, true); } + } + + + internal static string PackageDir { + private set { projectSettings.SetString(PackageDirKey, value); } + get { + return FileUtils.PosixPathSeparators(ValidatePackageDir( + ConfigurablePackageDir ? + (projectSettings.GetString(PackageDirKey, DefaultPackageDir)) : + DefaultPackageDir)); + } + } + + internal static bool AutoResolutionDisabledWarning { + set { projectSettings.SetBool(AutoResolutionDisabledWarningKey, value); } + get { return projectSettings.GetBool(AutoResolutionDisabledWarningKey, true); } + } + + /// + /// This setting is not exposed in the Settings menu but is + /// leveraged by the PlayServicesResolver to determine whether to + /// display a prompt. + /// + internal static bool PromptBeforeAutoResolution { + set { + projectSettings.SetBool(PromptBeforeAutoResolutionKey, value); + } + get { return projectSettings.GetBool(PromptBeforeAutoResolutionKey, true); } + } + + internal static bool UseProjectSettings { + get { return projectSettings.UseProjectSettings; } + set { projectSettings.UseProjectSettings = value; } + } + + // Whether AARs that use variable expansion should be exploded when Gradle builds are + // enabled. + internal static bool ExplodeAars { + set { projectSettings.SetBool(ExplodeAarsKey, value); } + get { return projectSettings.GetBool(ExplodeAarsKey, true); } + } + + internal static string AndroidManifestPath { + get { + return FileUtils.PosixPathSeparators( + Path.Combine(PackageDir, "AndroidManifest.xml")); + } + } + + internal static bool PatchAndroidManifest { + set { projectSettings.SetBool(PatchAndroidManifestKey, value); } + get { return projectSettings.GetBool(PatchAndroidManifestKey, true); } + } + + internal static bool PatchMainTemplateGradle { + set { projectSettings.SetBool(PatchMainTemplateGradleKey, value); } + get { return projectSettings.GetBool(PatchMainTemplateGradleKey, true); } + } + + internal static bool PatchPropertiesTemplateGradle { + set { projectSettings.SetBool(PatchPropertiesTemplateGradleKey, value); } + get { return projectSettings.GetBool(PatchPropertiesTemplateGradleKey, true); } + } + + internal static bool PatchSettingsTemplateGradle { + set { projectSettings.SetBool(PatchSettingsTemplateGradleKey, value); } + get { return projectSettings.GetBool(PatchSettingsTemplateGradleKey, true); } + } + + internal static bool UseFullCustomMavenRepoPathWhenExport { + set { projectSettings.SetBool(UseFullCustomMavenRepoPathWhenExportKey, value); } + get { return projectSettings.GetBool(UseFullCustomMavenRepoPathWhenExportKey, true); } + } + + internal static bool UseFullCustomMavenRepoPathWhenNotExport { + set { projectSettings.SetBool(UseFullCustomMavenRepoPathWhenNotExportKey, value); } + get { return projectSettings.GetBool(UseFullCustomMavenRepoPathWhenNotExportKey, false); } + } + + internal static string LocalMavenRepoDir { + private set { projectSettings.SetString(LocalMavenRepoDirKey, value); } + get { + return FileUtils.PosixPathSeparators(ValidateLocalMavenRepoDir( + projectSettings.GetString(LocalMavenRepoDirKey, DefaultLocalMavenRepoDir))); + } + } + + internal static bool UseJetifier { + set { projectSettings.SetBool(UseJetifierKey, value); } + get { return projectSettings.GetBool(UseJetifierKey, true); } + } + + internal static bool VerboseLogging { + private set { projectSettings.SetBool(VerboseLoggingKey, value); } + get { return projectSettings.GetBool(VerboseLoggingKey, false); } + } + + internal static bool UserRejectedGradleUpgrade { + set { projectSettings.SetBool(UserRejectedGradleUpgradeKey, value); } + get { return projectSettings.GetBool(UserRejectedGradleUpgradeKey, false); } + } + + internal static string ValidatePackageDir(string directory) { + // Make sure the package directory starts with the same name. + // This is case insensitive to handle cases where developers rename Unity + // project directories on Windows (which has a case insensitive file system by + // default) then they use the project on OSX / Linux. + if (!directory.ToLowerInvariant().StartsWith(AndroidPluginsDir.ToLower())) { + directory = AndroidPluginsDir; + } + directory = FileUtils.NormalizePathSeparators(directory); + var searchDirectory = FileUtils.FindDirectoryByCaseInsensitivePath(directory); + if (String.IsNullOrEmpty(searchDirectory)) searchDirectory = directory; + if (directory != searchDirectory && + (previouslyValidatedPackageDir == null || + searchDirectory != previouslyValidatedPackageDir)) { + PlayServicesResolver.Log( + String.Format("Resolving to Android package directory {0} instead of the " + + "requested target directory {1}\n" + + "\n" + + "Is {0} in a different case to {1} ?\n", + searchDirectory, directory), level: LogLevel.Warning); + directory = searchDirectory; + } else if ((previouslyValidatedPackageDir == null || + searchDirectory != previouslyValidatedPackageDir) && + searchDirectory == null) { + PlayServicesResolver.Log( + String.Format("Android package directory {0} not found.", + directory), level: LogLevel.Warning); + } + previouslyValidatedPackageDir = searchDirectory; + + return directory; + } + + internal static string ValidateLocalMavenRepoDir(string directory) { + if (!directory.ToLowerInvariant().StartsWith(FileUtils.ASSETS_FOLDER.ToLower())) { + directory = DefaultLocalMavenRepoDir; + } + directory = FileUtils.NormalizePathSeparators(directory); + + // TODO: Remove these restrictions + // Cannot set to be under "Assets/Plugins/Android". Seems like all .aar and .pom + // genereted under this folder will be removed in gradle template mode after + // being generated. Need to investigate why. + if (directory.StartsWith(AndroidPluginsDir)) { + directory = DefaultLocalMavenRepoDir; + PlayServicesResolver.Log(String.Format( + "Currently LocalMavenRepoDir does not work at any folder " + + "under \"Assets/Plugins/Android\""), level: LogLevel.Warning); + } + if (directory.EndsWith(Path.DirectorySeparatorChar.ToString())) { + directory = directory.Substring(0, directory.Length - 1); + } + + return directory; + } + + public void Initialize() { + minSize = new Vector2(425, 510); + position = new Rect(UnityEngine.Screen.width / 3, UnityEngine.Screen.height / 3, + minSize.x, minSize.y); + } + + public void LoadSettings() { + settings = new Settings(); + } + + public void OnEnable() { + LoadSettings(); + } + + /// + /// Called when the GUI should be rendered. + /// + public void OnGUI() { + GUI.skin.label.wordWrap = true; + + GUILayout.BeginVertical(); + GUILayout.Label(String.Format("Android Resolver (version {0}.{1}.{2})", + AndroidResolverVersionNumber.Value.Major, + AndroidResolverVersionNumber.Value.Minor, + AndroidResolverVersionNumber.Value.Build)); + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + + GUI.skin.label.wordWrap = true; + GUILayout.BeginVertical(); + GUILayout.BeginHorizontal(); + GUILayout.Label("Use Gradle Daemon", EditorStyles.boldLabel); + settings.useGradleDaemon = EditorGUILayout.Toggle(settings.useGradleDaemon); + GUILayout.EndHorizontal(); + GUILayout.Label( + settings.useGradleDaemon ? + ("Gradle Daemon will be used to fetch dependencies. " + + "This is faster but can be flakey in some environments.") : + ("Gradle Daemon will not be used. This is slow but reliable.")); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Enable Auto-Resolution", EditorStyles.boldLabel); + settings.enableAutoResolution = EditorGUILayout.Toggle(settings.enableAutoResolution); + GUILayout.EndHorizontal(); + GUILayout.Label( + settings.enableAutoResolution ? + ("Android libraries will be downloaded and processed in the editor.") : + ("Android libraries will *not* be downloaded or processed in the editor.")); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Enable Resolution On Build", EditorStyles.boldLabel); + settings.autoResolveOnBuild = EditorGUILayout.Toggle(settings.autoResolveOnBuild); + GUILayout.EndHorizontal(); + GUILayout.Label( + settings.autoResolveOnBuild ? + ("Android libraries will be downloaded and processed in a pre-build step.") : + ("Android libraries will *not* be downloaded or processed in a pre-build step.")); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Install Android Packages", EditorStyles.boldLabel); + settings.installAndroidPackages = + EditorGUILayout.Toggle(settings.installAndroidPackages); + GUILayout.EndHorizontal(); + + if (ConfigurablePackageDir) { + GUILayout.BeginHorizontal(); + string previousPackageDir = settings.packageDir; + GUILayout.Label("Package Directory", EditorStyles.boldLabel); + if (GUILayout.Button("Browse")) { + string path = EditorUtility.OpenFolderPanel("Set Package Directory", + PackageDir, ""); + int startOfPath = path.IndexOf(AndroidPluginsDir); + settings.packageDir = FileUtils.PosixPathSeparators( + startOfPath < 0 ? "" : path.Substring(startOfPath, + path.Length - startOfPath)); + } + if (!previousPackageDir.Equals(settings.packageDir)) { + settings.packageDir = ValidatePackageDir(settings.packageDir); + } + GUILayout.EndHorizontal(); + settings.packageDir = FileUtils.PosixPathSeparators( + EditorGUILayout.TextField(settings.packageDir)); + } + + GUILayout.BeginHorizontal(); + GUILayout.Label("Explode AARs", EditorStyles.boldLabel); + settings.explodeAars = EditorGUILayout.Toggle(settings.explodeAars); + GUILayout.EndHorizontal(); + if (settings.explodeAars) { + GUILayout.Label("AARs will be exploded (unpacked) when ${applicationId} " + + "variable replacement is required in an AAR's " + + "AndroidManifest.xml or a single target ABI is selected " + + "without a compatible build system."); + } else { + GUILayout.Label("AAR explosion will be disabled." + + "You may need to set " + + "android.defaultConfig.applicationId to your bundle ID in your " + + "build.gradle to generate a functional APK."); + } + + // Disable the ability to toggle the auto-resolution disabled warning + // when auto resolution is enabled. + EditorGUI.BeginDisabledGroup(settings.enableAutoResolution || + settings.autoResolveOnBuild); + GUILayout.BeginHorizontal(); + GUILayout.Label("Auto-Resolution Disabled Warning", EditorStyles.boldLabel); + settings.autoResolutionDisabledWarning = + EditorGUILayout.Toggle(settings.autoResolutionDisabledWarning); + GUILayout.EndHorizontal(); + EditorGUI.EndDisabledGroup(); + + // Disable the ability to toggle the auto-resolution disabled warning + // when auto resolution is enabled. + EditorGUI.BeginDisabledGroup(!settings.enableAutoResolution); + GUILayout.BeginHorizontal(); + GUILayout.Label("Prompt Before Auto-Resolution", EditorStyles.boldLabel); + settings.promptBeforeAutoResolution = + EditorGUILayout.Toggle(settings.promptBeforeAutoResolution); + GUILayout.EndHorizontal(); + EditorGUI.EndDisabledGroup(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Patch AndroidManifest.xml", EditorStyles.boldLabel); + settings.patchAndroidManifest = EditorGUILayout.Toggle(settings.patchAndroidManifest); + GUILayout.EndHorizontal(); + if (settings.patchAndroidManifest) { + GUILayout.Label(String.Format( + "Instances of \"applicationId\" variable references will be replaced in " + + "{0} with the bundle ID. If the bundle ID " + + "is changed the previous bundle ID will be replaced with the new " + + "bundle ID by the plugin.\n\n" + + "This works around a bug in Unity 2018.x where the " + + "\"applicationId\" variable is not replaced correctly.", + AndroidManifestPath)); + } else { + GUILayout.Label(String.Format( + "{0} is not modified.\n\n" + + "If you're using Unity 2018.x and have an AndroidManifest.xml " + + "that uses the \"applicationId\" variable, your build may fail.", + AndroidManifestPath)); + } + + GUILayout.BeginHorizontal(); + GUILayout.Label("Use Jetifier.", EditorStyles.boldLabel); + settings.useJetifier = EditorGUILayout.Toggle(settings.useJetifier); + GUILayout.EndHorizontal(); + if (settings.useJetifier) { + GUILayout.Label( + "Legacy Android support libraries and references to them from other " + + "libraries will be rewritten to use Jetpack using the Jetifier tool. " + + "Enabling option allows an application to use Android Jetpack " + + "when other libraries in the project use the Android support libraries."); + } else { + GUILayout.Label( + "Class References to legacy Android support libraries (pre-Jetpack) will be " + + "left unmodified in the project. This will possibly result in broken Android " + + "builds when mixing legacy Android support libraries and Jetpack libraries."); + } + + GUILayout.BeginHorizontal(); + GUILayout.Label("Disable MainTemplate Gradle prompt", EditorStyles.boldLabel); + settings.userRejectedGradleUpgrade = + EditorGUILayout.Toggle(settings.userRejectedGradleUpgrade); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Patch mainTemplate.gradle", EditorStyles.boldLabel); + settings.patchMainTemplateGradle = + EditorGUILayout.Toggle(settings.patchMainTemplateGradle); + GUILayout.EndHorizontal(); + if (settings.patchMainTemplateGradle) { + GUILayout.Label( + "If Gradle builds are enabled and a mainTemplate.gradle file is present, " + + "the mainTemplate.gradle file will be patched with dependencies managed " + + "by the Android Resolver."); + } else { + GUILayout.Label(String.Format( + "If Gradle builds are enabled and a mainTemplate.gradle file is present, " + + "the mainTemplate.gradle file will not be modified. Instead dependencies " + + "managed by the Android Resolver will be added to the project under {0}", + settings.packageDir)); + } + + if (settings.patchMainTemplateGradle) { + GUILayout.Label("Use Full Custom Local Maven Repo Path", EditorStyles.boldLabel); + GUILayout.BeginHorizontal(); + GUILayout.Label(" When building Android app through Unity", EditorStyles.boldLabel); + settings.useFullCustomMavenRepoPathWhenNotExport = + EditorGUILayout.Toggle(settings.useFullCustomMavenRepoPathWhenNotExport); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + GUILayout.Label(" When exporting Android project", EditorStyles.boldLabel); + settings.useFullCustomMavenRepoPathWhenExport = + EditorGUILayout.Toggle(settings.useFullCustomMavenRepoPathWhenExport); + GUILayout.EndHorizontal(); + + GUILayout.Label( + "EDM4U can inject custom local Maven repo to Gradle template files " + + "differnetly depending on whether 'Export Project' in Build Settings is " + + "enabled or not.\n" + + "If checked, custom local Maven repo path will look like the following. " + + "This is best if the Unity project is always under the same path, or when " + + "Unity editor has bugs which fail to resolve template variables like " + + "'**DIR_UNITYPROJECT**'"); + GUILayout.Box( + " maven {\n" + + " url \"file:////path/to/myUnityProject/path/to/m2repository\"\n" + + " }", EditorStyles.wordWrappedMiniLabel); + GUILayout.Label( + "If unchecked, custom local Maven repo path will look like the following. " + + "This is best if the Unity projects locates in different folders on " + + "different workstations. 'unityProjectPath' will be resolved at build time " + + "using template variables like '**DIR_UNITYPROJECT**'"); + GUILayout.Box( + " def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace(\"\\\", \"/\")\n" + + " maven {\n" + + " url (unityProjectPath + \"/path/to/m2repository\")\n" + + " }", EditorStyles.wordWrappedMiniLabel); + GUILayout.Label( + "Note that EDM4U always uses full path if the custom local Maven repo is NOT " + + "under Unity project folder."); + + GUILayout.BeginHorizontal(); + string previousDir = settings.localMavenRepoDir; + GUILayout.Label("Local Maven Repo Directory", EditorStyles.boldLabel); + if (GUILayout.Button("Browse")) { + string path = EditorUtility.OpenFolderPanel("Set Local Maven Repo Directory", + settings.localMavenRepoDir, ""); + int startOfPath = path.IndexOf( + FileUtils.ASSETS_FOLDER + Path.DirectorySeparatorChar); + settings.localMavenRepoDir = FileUtils.PosixPathSeparators( + startOfPath < 0 ? DefaultLocalMavenRepoDir : + path.Substring(startOfPath, path.Length - startOfPath)); + } + if (!previousDir.Equals(settings.localMavenRepoDir)) { + settings.localMavenRepoDir = + ValidateLocalMavenRepoDir(settings.localMavenRepoDir); + } + GUILayout.EndHorizontal(); + GUILayout.Label( + "Please pick a folder under Assets folder. Currently it won't work at " + + "any folder under \"Assets/Plugins/Android\""); + settings.localMavenRepoDir = FileUtils.PosixPathSeparators( + ValidateLocalMavenRepoDir(EditorGUILayout.TextField( + settings.localMavenRepoDir))); + } + + if (settings.useJetifier) { + GUILayout.BeginHorizontal(); + GUILayout.Label("Patch gradleTemplate.properties", EditorStyles.boldLabel); + settings.patchPropertiesTemplateGradle = EditorGUILayout.Toggle(settings.patchPropertiesTemplateGradle); + GUILayout.EndHorizontal(); + GUILayout.Label( + "For Unity 2019.3 and above, it is recommended to enable Jetifier " + + "and AndroidX via gradleTemplate.properties. Please enable " + + "Custom Gradle Properties Template' found under 'Player Settings > " + + "Settings for Android > Publishing Settings' menu item. " + + "This has no effect in older versions of Unity."); + } + + if (settings.patchMainTemplateGradle) { + GUILayout.BeginHorizontal(); + GUILayout.Label("Copy and patch settingsTemplate.gradle from 2022.2", EditorStyles.boldLabel); + settings.patchSettingsTemplateGradle = EditorGUILayout.Toggle(settings.patchSettingsTemplateGradle); + GUILayout.EndHorizontal(); + GUILayout.Label( + "For Unity 2022.2 and above, any additional Maven repositories should be " + + "specified in settingsTemplate.gradle. If checked, EDM4U will also copy " + + "settingsTemplate.gradle from Unity engine folder."); + } + + settings.analyticsSettings.RenderGui(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Verbose Logging", EditorStyles.boldLabel); + settings.verboseLogging = EditorGUILayout.Toggle(settings.verboseLogging); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Use project settings", EditorStyles.boldLabel); + settings.useProjectSettings = EditorGUILayout.Toggle(settings.useProjectSettings); + GUILayout.EndHorizontal(); + + GUILayout.EndVertical(); + EditorGUILayout.EndScrollView(); + + GUILayout.BeginVertical(); + GUILayout.Space(10); + + if (GUILayout.Button("Reset to Defaults")) { + // Load default settings into the dialog but preserve the state in the user's + // saved preferences. + var backupSettings = new Settings(); + RestoreDefaultSettings(); + PlayServicesResolver.analytics.Report("settings/reset", "Settings Reset"); + LoadSettings(); + backupSettings.Save(); + } + + GUILayout.BeginHorizontal(); + bool closeWindow = GUILayout.Button("Cancel"); + if (closeWindow) { + PlayServicesResolver.analytics.Report("settings/cancel", "Settings Cancel"); + } + bool ok = GUILayout.Button("OK"); + closeWindow |= ok; + if (ok) { + PlayServicesResolver.analytics.Report( + "settings/save", + new KeyValuePair[] { + new KeyValuePair( + "useGradleDaemon", + SettingsDialog.UseGradleDaemon.ToString()), + new KeyValuePair( + "enableAutoResolution", + SettingsDialog.EnableAutoResolution.ToString()), + new KeyValuePair( + "installAndroidPackages", + SettingsDialog.InstallAndroidPackages.ToString()), + new KeyValuePair( + "explodeAars", + SettingsDialog.ExplodeAars.ToString()), + new KeyValuePair( + "patchAndroidManifest", + SettingsDialog.PatchAndroidManifest.ToString()), + new KeyValuePair( + "UseFullCustomMavenRepoPathWhenNotExport", + SettingsDialog.UseFullCustomMavenRepoPathWhenNotExport.ToString()), + new KeyValuePair( + "UseFullCustomMavenRepoPathWhenExport", + SettingsDialog.UseFullCustomMavenRepoPathWhenExport.ToString()), + new KeyValuePair( + "localMavenRepoDir", + SettingsDialog.LocalMavenRepoDir.ToString()), + new KeyValuePair( + "useJetifier", + SettingsDialog.UseJetifier.ToString()), + new KeyValuePair( + "verboseLogging", + SettingsDialog.VerboseLogging.ToString()), + new KeyValuePair( + "autoResolutionDisabledWarning", + SettingsDialog.AutoResolutionDisabledWarning.ToString()), + new KeyValuePair( + "promptBeforeAutoResolution", + SettingsDialog.PromptBeforeAutoResolution.ToString()), + new KeyValuePair( + "patchMainTemplateGradle", + SettingsDialog.PatchMainTemplateGradle.ToString()), + new KeyValuePair( + "patchPropertiesTemplateGradle", + SettingsDialog.PatchPropertiesTemplateGradle.ToString()), + new KeyValuePair( + "patchSettingsTemplateGradle", + SettingsDialog.PatchSettingsTemplateGradle.ToString()), + new KeyValuePair( + "userRejectedGradleUpgrade", + SettingsDialog.UserRejectedGradleUpgrade.ToString()), + }, + "Settings Save"); + + settings.Save(); + PlayServicesResolver.OnSettingsChanged(); + } + if (closeWindow) Close(); + GUILayout.EndHorizontal(); + GUILayout.EndVertical(); + + GUILayout.EndVertical(); + } + } +} diff --git a/source/PlayServicesResolver/src/TextAreaDialog.cs b/source/AndroidResolver/src/TextAreaDialog.cs similarity index 72% rename from source/PlayServicesResolver/src/TextAreaDialog.cs rename to source/AndroidResolver/src/TextAreaDialog.cs index d6aea73e..93eadb13 100644 --- a/source/PlayServicesResolver/src/TextAreaDialog.cs +++ b/source/AndroidResolver/src/TextAreaDialog.cs @@ -99,6 +99,9 @@ public class TextAreaDialog : EditorWindow /// private const long REPAINT_PERIOD_IN_MILLISECONDS = 33; // ~30Hz + // Backing store for the Redirector property. + internal LogRedirector logRedirector; + /// /// Get the existing text area window or create a new one. /// @@ -124,12 +127,15 @@ public virtual void Initialize() minSize = new Vector2(300, 200); position = new Rect(UnityEngine.Screen.width / 3, UnityEngine.Screen.height / 3, minSize.x * 2, minSize.y * 2); + logRedirector = new LogRedirector(this); } // Add to body text. public void AddBodyText(string text) { - bodyText += text; - Repaint(); + RunOnMainThread.Run(() => { + bodyText += text; + Repaint(); + }); } /// @@ -168,10 +174,10 @@ protected virtual void OnGUI() const int chunkSize = 5000; // Conservative chunk size < 65k characters. while (bodyTextOffset < bodyText.Length) { - int readSize = chunkSize; - readSize = bodyTextOffset + readSize >= bodyText.Length ? - bodyText.Length - bodyTextOffset : readSize; - bodyTextList.Add(bodyText.Substring(bodyTextOffset, readSize)); + int searchSize = Math.Min(bodyText.Length - bodyTextOffset, chunkSize); + int readSize = bodyText.LastIndexOf("\n", bodyTextOffset + searchSize, searchSize); + readSize = readSize >= 0 ? readSize - bodyTextOffset + 1 : searchSize; + bodyTextList.Add(bodyText.Substring(bodyTextOffset, readSize).TrimEnd()); bodyTextOffset += readSize; } foreach (string bodyTextChunk in bodyTextList) @@ -222,6 +228,76 @@ protected virtual void OnDestroy() { if (buttonClicked != null) buttonClicked(this); } } + + /// + /// Redirects logs to a TextAreaDialog window. + /// + internal class LogRedirector { + + /// + /// Window to redirect logs to. + /// + private TextAreaDialog window; + + /// + /// Create a log redirector associated with this window. + /// + public LogRedirector(TextAreaDialog window) { + this.window = window; + LogToWindow = (string message, LogLevel level) => LogMessage(message, level); + ErrorLogged = false; + WarningLogged = false; + ShouldLogDelegate = () => { return true; }; + } + + /// + /// Delegate that logs to the window associated with this object. + /// + public Google.Logger.LogMessageDelegate LogToWindow { get; private set; } + + /// + /// Whether an error was logged. + /// + public bool ErrorLogged { get; private set; } + + + /// + /// Whether a warning was logged. + /// + public bool WarningLogged { get; private set; } + + /// + /// Delegate that determines whether a message should be logged. + /// + public Func ShouldLogDelegate { get; set; } + + /// + /// Log a message to the window associated with this object. + /// + private void LogMessage(string message, LogLevel level) { + string messagePrefix; + switch (level) { + case LogLevel.Error: + messagePrefix = "ERROR: "; + ErrorLogged = true; + break; + case LogLevel.Warning: + messagePrefix = "WARNING: "; + WarningLogged = true; + break; + default: + messagePrefix = ""; + break; + } + if (ShouldLogDelegate()) window.AddBodyText(messagePrefix + message + "\n"); + + } + } + + /// + /// Get an object that can redirect log messages to this window. + /// + internal LogRedirector Redirector { get { return logRedirector; } } } } diff --git a/source/PlayServicesResolver/src/UnityCompat.cs b/source/AndroidResolver/src/UnityCompat.cs similarity index 51% rename from source/PlayServicesResolver/src/UnityCompat.cs rename to source/AndroidResolver/src/UnityCompat.cs index ad3357be..265e19fb 100644 --- a/source/PlayServicesResolver/src/UnityCompat.cs +++ b/source/AndroidResolver/src/UnityCompat.cs @@ -30,14 +30,17 @@ public class UnityCompat { private const string Namespace = "GooglePlayServices."; private const string ANDROID_MIN_SDK_FALLBACK_KEY = Namespace + "MinSDKVersionFallback"; private const string ANDROID_PLATFORM_FALLBACK_KEY = Namespace + "PlatformVersionFallback"; - private const string ANDROID_BUILD_TOOLS_FALLBACK_KEY = Namespace + "BuildToolsVersionFallback"; private const int DEFAULT_ANDROID_MIN_SDK = 14; private const int DEFAULT_PLATFORM_VERSION = 25; - private const string DEFAULT_BUILD_TOOLS_VERSION = "25.0.2"; private const string UNITY_ANDROID_VERSION_ENUM_PREFIX = "AndroidApiLevel"; + private const string UNITY_ANDROID_MIN_SDK_VERSION_PROPERTY = "minSdkVersion"; + private const string UNITY_ANDROID_TARGET_SDK_VERSION_PROPERTY = "targetSdkVersion"; private const string UNITY_ANDROID_EXTENSION_ASSEMBLY = "UnityEditor.Android.Extensions"; + private const string UNITY_ANDROID_JAVA_TOOLS_CLASS = "UnityEditor.Android.AndroidJavaTools"; private const string UNITY_ANDROID_SDKTOOLS_CLASS = "UnityEditor.Android.AndroidSDKTools"; + private const string UNITY_ANDROID_EXTERNAL_TOOLS_SETTINGS_CLASS = + "UnityEditor.Android.AndroidExternalToolsSettings"; private const string UNITY_ANDROID_POST_PROCESS_ANDROID_PLAYER_CLASS = "UnityEditor.Android.PostProcessAndroidPlayer"; private const string WRITE_A_BUG = @@ -50,18 +53,23 @@ private static int MinSDKVersionFallback { private static int AndroidPlatformVersionFallback { get { return EditorPrefs.GetInt(ANDROID_PLATFORM_FALLBACK_KEY, DEFAULT_PLATFORM_VERSION); } } - private static string AndroidBuildToolsVersionFallback { - get { - return EditorPrefs.GetString(ANDROID_BUILD_TOOLS_FALLBACK_KEY, - DEFAULT_BUILD_TOOLS_VERSION); - } - } - - private const float EPSILON = 1e-7f; // Parses a UnityEditor.AndroidSDKVersion enum for a value. - private static int VersionFromAndroidSDKVersionsEnum(string enumName, string fallbackPrefKey, + private static int VersionFromAndroidSDKVersionsEnum(object enumValue, string fallbackPrefKey, int fallbackValue) { + string enumName = null; + try { + enumName = Enum.GetName(typeof(AndroidSdkVersions), enumValue); + } catch (ArgumentException) { + //Fall back on auto if the enum value is not parsable + return -1; + } + + // If the enumName is empty then enumValue was not represented in the enum, + // most likely because Unity has not yet added the new version, + // fall back on the raw enumValue + if (String.IsNullOrEmpty(enumName)) return (int)enumValue; + if (enumName.StartsWith(UNITY_ANDROID_VERSION_ENUM_PREFIX)) { enumName = enumName.Substring(UNITY_ANDROID_VERSION_ENUM_PREFIX.Length); } @@ -92,13 +100,22 @@ private static int VersionFromAndroidSDKVersionsEnum(string enumName, string fal /// the sdk value (ie. 24 for Android 7.0 Nouget) public static int GetAndroidMinSDKVersion() { int minSdkVersion = VersionFromAndroidSDKVersionsEnum( - PlayerSettings.Android.minSdkVersion.ToString(), + (object)PlayerSettings.Android.minSdkVersion, ANDROID_MIN_SDK_FALLBACK_KEY, MinSDKVersionFallback); if (minSdkVersion == -1) return MinSDKVersionFallback; return minSdkVersion; } + /// + /// Try to set the min Android SDK version. + /// + /// SDK version to use, -1 for auto (newest installed SDK). + /// true if successful, false otherwise. + public static bool SetAndroidMinSDKVersion(int sdkVersion) { + return SetAndroidSDKVersion(sdkVersion, UNITY_ANDROID_MIN_SDK_VERSION_PROPERTY); + } + /// /// Parses the TargetSDK as an int from the Android Unity Player Settings. /// @@ -107,13 +124,40 @@ public static int GetAndroidMinSDKVersion() { /// from internal Unity APIs. This enum isn't guaranteed to return a value; it may return auto /// which means the implementation will have to decide what version to use. /// - /// The sdk value (ie. 24 for Android 7.0 Nougat). -1 means auto select. + /// The sdk value (ie. 24 for Android 7.0 Nougat). If the newest Android SDK can't + /// be found this returns the value of the AndroidPlatformVersionFallback property. public static int GetAndroidTargetSDKVersion() { var property = typeof(UnityEditor.PlayerSettings.Android).GetProperty("targetSdkVersion"); - return property == null ? -1 : + int apiLevel = property == null ? -1 : VersionFromAndroidSDKVersionsEnum( - Enum.GetName(property.PropertyType, property.GetValue(null, null)), + property.GetValue(null, null), ANDROID_PLATFORM_FALLBACK_KEY, AndroidPlatformVersionFallback); + if (apiLevel >= 0) return apiLevel; + return FindNewestInstalledAndroidSDKVersion(); + } + + /// + /// Try to set the target Android SDK version. + /// + /// SDK version to use, -1 for auto (newest installed SDK). + /// true if successful, false otherwise. + public static bool SetAndroidTargetSDKVersion(int sdkVersion) { + return SetAndroidSDKVersion(sdkVersion, UNITY_ANDROID_TARGET_SDK_VERSION_PROPERTY); + } + + private static bool SetAndroidSDKVersion(int sdkVersion, string propertyName) { + var property = typeof(UnityEditor.PlayerSettings.Android).GetProperty(propertyName); + if (property == null) return false; + var enumValueString = sdkVersion >= 0 ? + UNITY_ANDROID_VERSION_ENUM_PREFIX + sdkVersion.ToString() : + UNITY_ANDROID_VERSION_ENUM_PREFIX + "Auto"; + try { + property.SetValue(null, Enum.Parse(property.PropertyType, enumValueString), null); + return true; + } catch (ArgumentException) { + // Ignore. + } + return false; } /// @@ -122,33 +166,110 @@ public static int GetAndroidTargetSDKVersion() { [ObsoleteAttribute("InBatchMode is obsolete, use ExecutionEnvironment.InBatchMode instead")] public static bool InBatchMode { get { return ExecutionEnvironment.InBatchMode; } } - private static Type AndroidSDKToolsClass { + private static Type AndroidExternalToolsClass { get { - Type sdkTools = Type.GetType( - UNITY_ANDROID_SDKTOOLS_CLASS + ", " + UNITY_ANDROID_EXTENSION_ASSEMBLY); - if (sdkTools == null) { - Debug.LogError("Could not find the " + - UNITY_ANDROID_SDKTOOLS_CLASS + " class via reflection. " + WRITE_A_BUG); - return null; + return Type.GetType(UNITY_ANDROID_EXTERNAL_TOOLS_SETTINGS_CLASS + ", " + + UNITY_ANDROID_EXTENSION_ASSEMBLY); + } + } + + /// + /// Get a property of the UnityEditor.Android.AndroidExternalToolsSettings class which was + /// introduced in Unity 2019. + /// + /// Name of the property to query. + /// Value of the string property or null if the property isn't found or isn't a + /// string. + private static string GetAndroidExternalToolsSettingsProperty(string propertyName) { + string value = null; + var androidExternalTools = AndroidExternalToolsClass; + if (androidExternalTools != null) { + var property = androidExternalTools.GetProperty(propertyName); + if (property != null) { + value = property.GetValue(null, null) as string; } - return sdkTools; + } + return value; + } + + /// + /// Get the JDK path from UnityEditor.Android.AndroidExternalToolsSettings if it's + /// available. + /// + public static string AndroidExternalToolsSettingsJdkRootPath { + get { return GetAndroidExternalToolsSettingsProperty("jdkRootPath"); } + } + + /// + /// Get the Android NDK path from UnityEditor.Android.AndroidExternalToolsSettings if it's + /// available. + /// + public static string AndroidExternalToolsSettingsNdkRootPath { + get { return GetAndroidExternalToolsSettingsProperty("ndkRootPath"); } + } + + /// + /// Get the Android SDK path from UnityEditor.Android.AndroidExternalToolsSettings if it's + /// available. + /// + public static string AndroidExternalToolsSettingsSdkRootPath { + get { return GetAndroidExternalToolsSettingsProperty("sdkRootPath"); } + } + + /// + /// Get the Gradle path from UnityEditor.Android.AndroidExternalToolsSettings if it's + /// available. + /// + public static string AndroidExternalToolsSettingsGradlePath { + get { return GetAndroidExternalToolsSettingsProperty("gradlePath"); } + } + + private static Type AndroidJavaToolsClass { + get { + return Type.GetType( + UNITY_ANDROID_JAVA_TOOLS_CLASS + ", " + UNITY_ANDROID_EXTENSION_ASSEMBLY); } } - private static object SDKToolsInst { + private static object AndroidJavaToolsInstance { get { - MethodInfo getInstanceMethod = null; - Type sdkClass = AndroidSDKToolsClass; - if (sdkClass != null) - getInstanceMethod = sdkClass.GetMethod("GetInstance"); - if (getInstanceMethod == null) { - Debug.LogError("Could not find the " + - "AndroidSDKToolsClass.GetInstance method via reflection. " + WRITE_A_BUG); + try { + return VersionHandler.InvokeStaticMethod(AndroidJavaToolsClass, "GetInstance", + null); + } catch (Exception) { return null; } + } + } + + private static Type AndroidSDKToolsClass { + get { + return Type.GetType(UNITY_ANDROID_SDKTOOLS_CLASS + ", " + + UNITY_ANDROID_EXTENSION_ASSEMBLY); + } + } - return getInstanceMethod.Invoke(null, BindingFlags.NonPublic | BindingFlags.Static, - null, new object[] {}, null); + private static object AndroidSDKToolsInstance { + get { + var androidSdkToolsClass = AndroidSDKToolsClass; + if (androidSdkToolsClass == null) { + Debug.LogError(String.Format("{0} class does not exist. {1}", + UNITY_ANDROID_SDKTOOLS_CLASS, WRITE_A_BUG)); + return null; + } + try { + return VersionHandler.InvokeStaticMethod(androidSdkToolsClass, "GetInstance", null); + } catch (Exception) { + } + // Unity 2018+ requires the AndroidJavaTools instance to get the SDK tools instance. + try { + return VersionHandler.InvokeStaticMethod(androidSdkToolsClass, "GetInstance", + new object[] { AndroidJavaToolsInstance }); + } catch (Exception e) { + Debug.LogError(String.Format("Failed while calling {0}.GetInstance() ({1}). {2}", + UNITY_ANDROID_SDKTOOLS_CLASS, e, WRITE_A_BUG)); + } + return null; } } @@ -167,7 +288,7 @@ private static Type PostProcessAndroidPlayerClass { } } - private static object PostProcessAndroidPlayerInst { + private static object PostProcessAndroidPlayerInstance { get { Type androidPlayerClass = PostProcessAndroidPlayerClass; ConstructorInfo constructor = null; @@ -185,64 +306,101 @@ private static object PostProcessAndroidPlayerInst { } } + // Display a warning and get the fallback Android SDK version. + private static int WarnOnAndroidSdkFallbackVersion() { + int version = AndroidPlatformVersionFallback; + Debug.LogWarning(String.Format( + "Failed to determine the most recently installed Android SDK version. {0} " + + "Resorting to reading a fallback value from the editor preferences {1}: {2}", + WRITE_A_BUG, ANDROID_PLATFORM_FALLBACK_KEY, version)); + return version; + } + // Gets the latest SDK version that's currently installed. // This is required for generating gradle builds. - [Obsolete] - public static int GetAndroidPlatform() { - MethodInfo platformVersionMethod = null; - Type sdkClass = AndroidSDKToolsClass; - if (sdkClass != null) { - platformVersionMethod = sdkClass.GetMethod("GetTopAndroidPlatformAvailable"); - if (platformVersionMethod != null) { - return (int)platformVersionMethod.Invoke(SDKToolsInst, - BindingFlags.NonPublic, null, new object[] { null }, null); + public static int FindNewestInstalledAndroidSDKVersion() { + var androidSdkToolsClass = AndroidSDKToolsClass; + if (androidSdkToolsClass != null) { + // To fetch the Android SDK version Unity runs tools in the Android SDK but doesn't + // set the JAVA_HOME environment variable before launching them via these internal + // methods. Therefore, we override JAVA_HOME in the process with the directory specified + // in Unity's settings while querying the Android SDK and then restore the JAVA_HOME + // variable to it's default state when we're finished. + var previousJavaHome = Environment.GetEnvironmentVariable(JavaUtilities.JAVA_HOME); + var javaHome = JavaUtilities.JavaHome; + if (!String.IsNullOrEmpty(javaHome)) { + Environment.SetEnvironmentVariable(JavaUtilities.JAVA_HOME, javaHome, + EnvironmentVariableTarget.Process); + } + try { + var androidSdkToolsInstance = AndroidSDKToolsInstance; + if (androidSdkToolsInstance == null) return WarnOnAndroidSdkFallbackVersion(); + // Unity 2019+ only has a method to list the installed targets, so we need to parse + // the returned list to determine the newest Android SDK version. + var listTargetPlatforms = androidSdkToolsClass.GetMethod( + "ListTargetPlatforms", BindingFlags.Instance | BindingFlags.NonPublic); + if (listTargetPlatforms != null) { + var sdkVersionList = new List(); + const string PLATFORM_STRING_PREFIX = "android-"; + IEnumerable platformStrings = null; + try { + // Unity 2019+ + platformStrings = (IEnumerable)listTargetPlatforms.Invoke( + androidSdkToolsInstance, null); + } catch (Exception) { + } + if (platformStrings != null) { + try { + // Unity 2018+ + platformStrings = (IEnumerable)listTargetPlatforms.Invoke( + androidSdkToolsInstance, new object[] { AndroidJavaToolsInstance }); + } catch (Exception) { + } + } + if (platformStrings != null) { + foreach (var platformString in platformStrings) { + if (platformString.StartsWith(PLATFORM_STRING_PREFIX)) { + int sdkVersion; + if (Int32.TryParse( + platformString.Substring(PLATFORM_STRING_PREFIX.Length), + out sdkVersion)) { + sdkVersionList.Add(sdkVersion); + } + } + } + sdkVersionList.Sort(); + var numberOfSdks = sdkVersionList.Count; + if (numberOfSdks > 0) { + return sdkVersionList[numberOfSdks - 1]; + } + } + } + var getTopAndroidPlatformAvailable = + androidSdkToolsClass.GetMethod("GetTopAndroidPlatformAvailable"); + if (getTopAndroidPlatformAvailable != null) { + return (int)getTopAndroidPlatformAvailable.Invoke( + androidSdkToolsInstance, BindingFlags.NonPublic, null, + new object[] { null }, null); + } + } finally { + Environment.SetEnvironmentVariable(JavaUtilities.JAVA_HOME, previousJavaHome, + EnvironmentVariableTarget.Process); } } // In Unity 4.x the method to get the platform was different and complex enough that // another function was was made to get the value unfortunately on another class. - sdkClass = PostProcessAndroidPlayerClass; - if (sdkClass != null) { - platformVersionMethod = sdkClass.GetMethod("GetAndroidPlatform", - BindingFlags.Instance | BindingFlags.NonPublic); - if (platformVersionMethod != null) { - object inst = PostProcessAndroidPlayerInst; - return (int)platformVersionMethod.Invoke(inst, - BindingFlags.NonPublic, null, new object[] {}, null); - } - } - - Debug.LogError(String.Format( - "Could not find the {0}.GetTopAndroidPlatformAvailable or " + - "{1}.GetAndroidPlatform methods via reflection. {2} Resorting to reading a fallback " + - "value from the editor preferences {3}: {4}", - UNITY_ANDROID_SDKTOOLS_CLASS, UNITY_ANDROID_POST_PROCESS_ANDROID_PLAYER_CLASS, - WRITE_A_BUG, ANDROID_PLATFORM_FALLBACK_KEY, AndroidPlatformVersionFallback)); - - return AndroidPlatformVersionFallback; - } - - // Finds the latest build tools version that's currently installed. - // This is required for generating gradle builds. - [Obsolete] - public static string GetAndroidBuildToolsVersion() { - // TODO: We're using reflection to access Unity's SDK helpers to get at this info - // but since this is likely fragile, and may not even be consistent across versions - // of Unity, we'll need to replace it with our own. - MethodInfo buildToolsVersionMethod = null; - Type sdkClass = AndroidSDKToolsClass; - if (sdkClass != null) - buildToolsVersionMethod = sdkClass.GetMethod("BuildToolsVersion"); - if (buildToolsVersionMethod != null) { - return (string)buildToolsVersionMethod.Invoke(SDKToolsInst, - BindingFlags.NonPublic, null, new object[] { null }, null); + var postProcessAndroidPlayerClass = PostProcessAndroidPlayerClass; + MethodInfo getAndroidPlatform = + postProcessAndroidPlayerClass != null ? + postProcessAndroidPlayerClass.GetMethod( + "GetAndroidPlatform", BindingFlags.Instance | BindingFlags.NonPublic) : null; + if (getAndroidPlatform != null) { + return (int)getAndroidPlatform.Invoke( + PostProcessAndroidPlayerInstance, BindingFlags.NonPublic, null, + new object[] {}, null); } - - Debug.LogError("Could not find the " + UNITY_ANDROID_SDKTOOLS_CLASS + - ".BuildToolsVersion method via reflection. " + WRITE_A_BUG + - " Resorting to reading a fallback value from the editor preferences " + - ANDROID_BUILD_TOOLS_FALLBACK_KEY + ": " + AndroidBuildToolsVersionFallback); - return AndroidBuildToolsVersionFallback; + return WarnOnAndroidSdkFallbackVersion(); } /// @@ -250,7 +408,7 @@ public static string GetAndroidBuildToolsVersion() { /// /// Unfortunately the Unity API does not provide a way to get the current BuildTargetGroup from /// the currently active BuildTarget. - /// BuildTarget to convert. + /// BuildTarget to convert. /// BuildTargetGroup enum value. private static BuildTargetGroup ConvertBuildTargetToBuildTargetGroup(BuildTarget buildTarget) { var buildTargetToGroup = new Dictionary() { @@ -286,7 +444,7 @@ private static BuildTargetGroup ConvertBuildTargetToBuildTargetGroup(BuildTarget /// Application identifier if it can be retrieved, null otherwise. private static string GetUnity56AndAboveApplicationIdentifier(BuildTarget buildTarget) { var getApplicationIdentifierMethod = - typeof(UnityEditor.PlayerSettings).GetMethod("GetApplicationIdentifier"); + typeof(UnityEditor.PlayerSettings).GetMethod("GetApplicationIdentifier", new[]{typeof(BuildTargetGroup)}); if (getApplicationIdentifierMethod == null) return null; var buildTargetGroup = ConvertBuildTargetToBuildTargetGroup(buildTarget); if (buildTargetGroup == BuildTargetGroup.Unknown) return null; @@ -304,7 +462,9 @@ private static string GetUnity56AndAboveApplicationIdentifier(BuildTarget buildT private static bool SetUnity56AndAboveApplicationIdentifier(BuildTarget buildTarget, string applicationIdentifier) { var setApplicationIdentifierMethod = - typeof(UnityEditor.PlayerSettings).GetMethod("SetApplicationIdentifier"); + typeof(UnityEditor.PlayerSettings).GetMethod( + "SetApplicationIdentifier", + new[]{typeof(BuildTargetGroup), typeof(string)}); if (setApplicationIdentifierMethod == null) return false; var buildTargetGroup = ConvertBuildTargetToBuildTargetGroup(buildTarget); if (buildTargetGroup == BuildTargetGroup.Unknown) return false; diff --git a/source/PlayServicesResolver/src/VersionNumber.cs b/source/AndroidResolver/src/VersionNumber.cs similarity index 95% rename from source/PlayServicesResolver/src/VersionNumber.cs rename to source/AndroidResolver/src/VersionNumber.cs index 1b0cb720..5309b99f 100644 --- a/source/PlayServicesResolver/src/VersionNumber.cs +++ b/source/AndroidResolver/src/VersionNumber.cs @@ -27,7 +27,7 @@ public class AndroidResolverVersionNumber { /// /// Version number, patched by the build process. /// - private const string VERSION_STRING = "1.2.111.0"; + private const string VERSION_STRING = "1.2.186"; /// /// Cached version structure. diff --git a/source/PlayServicesResolver/src/XmlDependencies.cs b/source/AndroidResolver/src/XmlDependencies.cs similarity index 80% rename from source/PlayServicesResolver/src/XmlDependencies.cs rename to source/AndroidResolver/src/XmlDependencies.cs index b8a9d07d..24a02594 100644 --- a/source/PlayServicesResolver/src/XmlDependencies.cs +++ b/source/AndroidResolver/src/XmlDependencies.cs @@ -18,6 +18,7 @@ namespace GooglePlayServices { using System; using System.Collections.Generic; + using System.IO; using System.Text.RegularExpressions; using Google; using UnityEditor; @@ -40,21 +41,29 @@ internal class XmlDependencies { /// protected string dependencyType = "dependencies"; + /// + /// Determines whether a filename matches an XML dependencies file. + /// + /// + /// true if it is a match, false otherwise. + internal bool IsDependenciesFile(string filename) { + foreach (var regex in fileRegularExpressions) { + if (regex.Match(filename).Success) { + return true; + } + } + return false; + } + /// /// Find all XML declared dependency files. /// /// List of XML dependency filenames in the project. private List FindFiles() { - var matchingFiles = new List(); - foreach (var assetGuid in AssetDatabase.FindAssets("t:TextAsset")) { - var dependencyFile = AssetDatabase.GUIDToAssetPath(assetGuid); - foreach (var regex in fileRegularExpressions) { - if (regex.Match(dependencyFile).Success) { - matchingFiles.Add(dependencyFile); - } - } - } - return matchingFiles; + return new List( + VersionHandlerImpl.SearchAssetDatabase( + "Dependencies t:TextAsset", IsDependenciesFile, + new [] { "Assets", "Packages"})); } /// diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTests.csproj b/source/AndroidResolver/test/AndroidResolverIntegrationTests.csproj new file mode 100644 index 00000000..4e6d28a1 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTests.csproj @@ -0,0 +1,74 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {6CB19B5A-371A-5E50-A3F7-E24F1696D501} + Library + Google + Google.AndroidResolverIntegrationTests + 1.2 + v3.5 + + + True + full + False + bin\Debug + DEBUG;UNITY_EDITOR + prompt + 4 + False + + + True + full + True + bin\Release + DEBUG;UNITY_EDITOR + prompt + 4 + False + + + ..\..\unity_dlls + + + + $(UnityHintPath)/UnityEditor.dll + + + $(UnityHintPath)/UnityEngine.dll + + + + + + + + + + + + + + + {5378B37A-887E-49ED-A8AE-42FA843AA9DC} + VersionHandler + + + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + VersionHandlerImpl + + + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040} + IntegrationTester + + + {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A} + AndroidResolver + + + diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestAdditionalDependencies.template b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestAdditionalDependencies.template similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestAdditionalDependencies.template rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestAdditionalDependencies.template diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.template b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.template new file mode 100644 index 00000000..ab1b4973 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.template @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestDependencies.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestDependencies.xml similarity index 84% rename from source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestDependencies.xml rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestDependencies.xml index 0f51534a..d44ed8d4 100644 --- a/source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestDependencies.xml +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestDependencies.xml @@ -8,6 +8,8 @@ Assets/Firebase/m2repository + + file:///my/nonexistant/test/repo diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.pom b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.pom similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.pom rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.pom diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.srcaar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.srcaar similarity index 99% rename from source/PlayServicesResolver/test/resolve_async/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.srcaar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.srcaar index b0fedd63..23794ccb 100755 Binary files a/source/PlayServicesResolver/test/resolve_async/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.srcaar and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.srcaar differ diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/gradleTemplateDISABLED.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/gradleTemplateDISABLED.properties new file mode 100644 index 00000000..de2dd054 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/gradleTemplateDISABLED.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M +org.gradle.parallel=true +unityStreamingAssets=**STREAMING_ASSETS** +**ADDITIONAL_PROPERTIES** diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/Plugins/Android/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/mainTemplateDISABLED.gradle similarity index 98% rename from source/PlayServicesResolver/test/resolve_async/Assets/Plugins/Android/mainTemplateDISABLED.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/mainTemplateDISABLED.gradle index eb52d001..5b7bb06a 100644 --- a/source/PlayServicesResolver/test/resolve_async/Assets/Plugins/Android/mainTemplateDISABLED.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/mainTemplateDISABLED.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/mainTemplateLibraryDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/mainTemplateLibraryDISABLED.gradle new file mode 100644 index 00000000..4dce823e --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/mainTemplateLibraryDISABLED.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.library' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/settingsTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/settingsTemplateDISABLED.gradle new file mode 100644 index 00000000..939fa3d5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/settingsTemplateDISABLED.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/android.arch.core.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.core.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/android.arch.core.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.core.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.runtime-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.runtime-1.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.runtime-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.runtime-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-annotations-26.1.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-annotations-26.1.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-annotations-26.1.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-ui-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-ui-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-ui-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-ui-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-utils-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-utils-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-utils-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-utils-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-fragment-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-fragment-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-fragment-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-fragment-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-media-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-media-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-media-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-media-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-v4-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-v4-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.android.support.support-v4-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-v4-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar similarity index 99% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-app-unity-5.1.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar index b0fedd63..23794ccb 100644 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-app-unity-5.1.1.aar and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-common-16.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-common-16.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-common-16.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-common-16.0.0.aar diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.core.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.core.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.core.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.core.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.runtime-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.runtime-1.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.runtime-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.runtime-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-annotations-26.1.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-annotations-26.1.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-annotations-26.1.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-ui-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-ui-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-ui-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-ui-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-utils-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-utils-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-utils-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-utils-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-fragment-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-fragment-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-fragment-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-fragment-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-media-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-media-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-media-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-media-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-v4-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-v4-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-v4-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-v4-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-base-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-base-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-base-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-base-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-basement-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-basement-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-basement-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-basement-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-tasks-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-tasks-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-tasks-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-tasks-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-app-unity-5.1.1.aar similarity index 99% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-app-unity-5.1.1.aar index b0fedd63..23794ccb 100644 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-common-16.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-common-16.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-common-16.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-common-16.0.0.aar diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/android.arch.core.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.core.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/android.arch.core.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.core.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.runtime-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.runtime-1.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.runtime-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.runtime-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-annotations-26.1.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-annotations-26.1.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-annotations-26.1.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-ui-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-ui-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-ui-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-ui-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-utils-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-utils-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-utils-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-utils-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-fragment-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-fragment-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-fragment-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-fragment-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-media-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-media-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-media-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-media-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-v4-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-v4-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-v4-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-v4-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar similarity index 99% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-app-unity-5.1.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar index b0fedd63..23794ccb 100644 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-app-unity-5.1.1.aar and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-common-16.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-common-16.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-common-16.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-common-16.0.0.aar diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.android.support.support-annotations-26.1.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.android.support.support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.android.support.support-annotations-26.1.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.android.support.support-annotations-26.1.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.google.firebase.firebase-app-unity-5.1.1.aar similarity index 99% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.google.firebase.firebase-app-unity-5.1.1.aar index b0fedd63..23794ccb 100644 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.android.support.support-annotations-26.1.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.android.support.support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.android.support.support-annotations-26.1.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.android.support.support-annotations-26.1.0.jar diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.google.firebase.firebase-app-unity-5.1.1.aar new file mode 100644 index 00000000..23794ccb Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplate.gradle similarity index 65% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplate.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplate.gradle index 21052427..5a09a8be 100644 --- a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplate.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplate.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } @@ -20,22 +19,22 @@ allprojects { } // Android Resolver Repos Start -allprojects { - repositories { +([rootProject] + (rootProject.subprojects as List)).each { project -> + project.repositories { + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") maven { url "https://maven.google.com/" } maven { - url "file:///my/nonexistant/test/repo" // Assets/PlayServicesResolver/Editor/TestDependencies.xml:15 + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 } maven { - url "file:///PROJECT_DIR/project_relative_path/repo" // Assets/PlayServicesResolver/Editor/TestDependencies.xml:15 + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 } maven { - url "file:///PROJECT_DIR/Assets/Firebase/m2repository" // Assets/PlayServicesResolver/Editor/TestDependencies.xml:10 + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 } mavenLocal() - jcenter() mavenCentral() } } @@ -45,12 +44,20 @@ apply plugin: 'com.android.application' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // Android Resolver Dependencies Start - compile 'com.android.support:support-annotations:26.1.0' // Assets/PlayServicesResolver/Editor/TestDependencies.xml:4 - compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/PlayServicesResolver/Editor/TestDependencies.xml:10 - compile 'com.google.firebase:firebase-common:16.0.0' // TestResolveAsync.SetupDependencies() + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 // Android Resolver Dependencies End **DEPS**} +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End android { compileSdkVersion **APIVERSION** buildToolsVersion '**BUILDTOOLS**' diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplateDISABLED.gradle similarity index 98% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplateDISABLED.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplateDISABLED.gradle index eb52d001..5b7bb06a 100644 --- a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplateDISABLED.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplateDISABLED.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/mainTemplate.gradle new file mode 100644 index 00000000..438e779b --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/mainTemplate.gradle @@ -0,0 +1,102 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +// Android Resolver Repos Start +([rootProject] + (rootProject.subprojects as List)).each { project -> + project.repositories { + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") + maven { + url "https://maven.google.com/" + } + maven { + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + } + mavenLocal() + mavenCentral() + } +} +// Android Resolver Repos End +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +// Android Resolver Dependencies Start + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + // compile 'com.google.android.gms:play-services-base:12.0.1' // Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.xml:6 + // compile 'com.google.android.gms:play-services-base:15.0.1' // Assets/ExternalDependencyManager/Editor/TestAdditionalDependencies.xml:3 + compile 'com.google.android.gms:play-services-base:18.0.1' // Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.xml:4 + // compile 'com.google.firebase:firebase-app-unity:5.+' // Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.xml:8 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 +// Android Resolver Dependencies End +**DEPS**} + +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/mainTemplateDISABLED.gradle similarity index 98% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplateDISABLED.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/mainTemplateDISABLED.gradle index eb52d001..5b7bb06a 100644 --- a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplateDISABLED.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/mainTemplateDISABLED.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/mainTemplate.gradle new file mode 100644 index 00000000..71d3fdee --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/mainTemplate.gradle @@ -0,0 +1,81 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +// Android Resolver Dependencies Start + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + // compile 'com.google.android.gms:play-services-base:12.0.1' // Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.xml:6 + // compile 'com.google.android.gms:play-services-base:15.0.1' // Assets/ExternalDependencyManager/Editor/TestAdditionalDependencies.xml:3 + compile 'com.google.android.gms:play-services-base:18.0.1' // Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.xml:4 + // compile 'com.google.firebase:firebase-app-unity:5.+' // Assets/ExternalDependencyManager/Editor/TestAdditionalDuplicateDependencies.xml:8 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 +// Android Resolver Dependencies End +**DEPS**} + +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/mainTemplateDISABLED.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/mainTemplateDISABLED.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/settingsTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/settingsTemplate.gradle new file mode 100644 index 00000000..d23796f5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/settingsTemplate.gradle @@ -0,0 +1,36 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() +// Android Resolver Repos Start + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") + maven { + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + } + mavenLocal() +// Android Resolver Repos End + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/settingsTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/settingsTemplateDISABLED.gradle new file mode 100644 index 00000000..939fa3d5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2/settingsTemplateDISABLED.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplate.gradle similarity index 98% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplate.gradle rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplate.gradle index eb52d001..5b7bb06a 100644 --- a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplate.gradle +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplate.gradle @@ -2,7 +2,6 @@ buildscript { repositories { - jcenter() google() } diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplateDISABLED.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplateDISABLED.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/mainTemplate.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/mainTemplate.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/mainTemplateDISABLED.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/mainTemplateDISABLED.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/settingsTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/settingsTemplate.gradle new file mode 100644 index 00000000..939fa3d5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/settingsTemplate.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/settingsTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/settingsTemplateDISABLED.gradle new file mode 100644 index 00000000..939fa3d5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2/settingsTemplateDISABLED.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplate.gradle new file mode 100644 index 00000000..cac35627 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplate.gradle @@ -0,0 +1,104 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +// Android Resolver Repos Start +([rootProject] + (rootProject.subprojects as List)).each { + ext { + it.setProperty("android.useAndroidX", true) + it.setProperty("android.enableJetifier", true) + } +} +([rootProject] + (rootProject.subprojects as List)).each { project -> + project.repositories { + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") + maven { + url "https://maven.google.com/" + } + maven { + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + } + mavenLocal() + mavenCentral() + } +} +// Android Resolver Repos End +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +// Android Resolver Dependencies Start + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 +// Android Resolver Dependencies End +**DEPS**} + +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplateDISABLED.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier/mainTemplateDISABLED.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2019_3/gradleTemplate.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2019_3/gradleTemplate.properties new file mode 100644 index 00000000..87046809 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2019_3/gradleTemplate.properties @@ -0,0 +1,8 @@ +org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M +org.gradle.parallel=true +unityStreamingAssets=**STREAMING_ASSETS** +# Android Resolver Properties Start +android.useAndroidX=true +android.enableJetifier=true +# Android Resolver Properties End +**ADDITIONAL_PROPERTIES** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2019_3/gradleTemplateDISABLED.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2019_3/gradleTemplateDISABLED.properties new file mode 100644 index 00000000..de2dd054 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2019_3/gradleTemplateDISABLED.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M +org.gradle.parallel=true +unityStreamingAssets=**STREAMING_ASSETS** +**ADDITIONAL_PROPERTIES** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2019_3/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2019_3/mainTemplate.gradle new file mode 100644 index 00000000..5a09a8be --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2019_3/mainTemplate.gradle @@ -0,0 +1,98 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +// Android Resolver Repos Start +([rootProject] + (rootProject.subprojects as List)).each { project -> + project.repositories { + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") + maven { + url "https://maven.google.com/" + } + maven { + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + } + mavenLocal() + mavenCentral() + } +} +// Android Resolver Repos End +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +// Android Resolver Dependencies Start + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 +// Android Resolver Dependencies End +**DEPS**} + +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2019_3/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2019_3/mainTemplateDISABLED.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2019_3/mainTemplateDISABLED.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/gradleTemplate.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/gradleTemplate.properties new file mode 100644 index 00000000..87046809 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/gradleTemplate.properties @@ -0,0 +1,8 @@ +org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M +org.gradle.parallel=true +unityStreamingAssets=**STREAMING_ASSETS** +# Android Resolver Properties Start +android.useAndroidX=true +android.enableJetifier=true +# Android Resolver Properties End +**ADDITIONAL_PROPERTIES** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/gradleTemplateDISABLED.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/gradleTemplateDISABLED.properties new file mode 100644 index 00000000..de2dd054 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/gradleTemplateDISABLED.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M +org.gradle.parallel=true +unityStreamingAssets=**STREAMING_ASSETS** +**ADDITIONAL_PROPERTIES** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/mainTemplate.gradle new file mode 100644 index 00000000..ddfc07b3 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/mainTemplate.gradle @@ -0,0 +1,77 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +// Android Resolver Dependencies Start + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 +// Android Resolver Dependencies End +**DEPS**} + +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/mainTemplateDISABLED.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/mainTemplateDISABLED.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/settingsTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/settingsTemplate.gradle new file mode 100644 index 00000000..d23796f5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/settingsTemplate.gradle @@ -0,0 +1,36 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() +// Android Resolver Repos Start + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") + maven { + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + } + mavenLocal() +// Android Resolver Repos End + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/settingsTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/settingsTemplateDISABLED.gradle new file mode 100644 index 00000000..939fa3d5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2/settingsTemplateDISABLED.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplate.gradle new file mode 100644 index 00000000..84def10b --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplate.gradle @@ -0,0 +1,98 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +// Android Resolver Repos Start +([rootProject] + (rootProject.subprojects as List)).each { project -> + project.repositories { + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") + maven { + url "https://maven.google.com/" + } + maven { + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + } + mavenLocal() + mavenCentral() + } +} +// Android Resolver Repos End +apply plugin: 'com.android.library' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +// Android Resolver Dependencies Start + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 +// Android Resolver Dependencies End +**DEPS**} + +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplateLibraryDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplateLibraryDISABLED.gradle new file mode 100644 index 00000000..4dce823e --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary/mainTemplateLibraryDISABLED.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.library' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/mainTemplate.gradle new file mode 100644 index 00000000..b93a44f8 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/mainTemplate.gradle @@ -0,0 +1,77 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.library' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +// Android Resolver Dependencies Start + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 +// Android Resolver Dependencies End +**DEPS**} + +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/mainTemplateLibraryDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/mainTemplateLibraryDISABLED.gradle new file mode 100644 index 00000000..4dce823e --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/mainTemplateLibraryDISABLED.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.library' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/settingsTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/settingsTemplate.gradle new file mode 100644 index 00000000..d23796f5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/settingsTemplate.gradle @@ -0,0 +1,36 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() +// Android Resolver Repos Start + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") + maven { + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + } + mavenLocal() +// Android Resolver Repos End + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/settingsTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/settingsTemplateDISABLED.gradle new file mode 100644 index 00000000..939fa3d5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2/settingsTemplateDISABLED.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/mainTemplate.gradle new file mode 100644 index 00000000..ddfc07b3 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/mainTemplate.gradle @@ -0,0 +1,77 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +// Android Resolver Dependencies Start + compile 'com.android.support:support-annotations:26.1.0' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4 + compile 'com.google.firebase:firebase-app-unity:5.1.1' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + compile 'com.google.firebase:firebase-common:16.0.0' // Google.AndroidResolverIntegrationTests.SetupDependencies + compile 'org.test.psr:classifier:1.0.1:foo@aar' // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12 +// Android Resolver Dependencies End +**DEPS**} + +// Android Resolver Exclusions Start +android { + packagingOptions { + exclude ('lib/unsupported/libFirebaseCppApp-5.1.1.so') + } +} +// Android Resolver Exclusions End +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/mainTemplateDISABLED.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/mainTemplateDISABLED.gradle @@ -0,0 +1,64 @@ +// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN + +buildscript { + repositories { + google() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + } +} + +allprojects { + repositories { + flatDir { + dirs 'libs' + } + } +} + +apply plugin: 'com.android.application' + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +**DEPS**} + +android { + compileSdkVersion **APIVERSION** + buildToolsVersion '**BUILDTOOLS**' + + defaultConfig { + minSdkVersion **MINSDKVERSION** + targetSdkVersion **TARGETSDKVERSION** + applicationId '**APPLICATIONID**' + ndk { + abiFilters **ABIFILTERS** + } + versionCode **VERSIONCODE** + versionName '**VERSIONNAME**' + } + + lintOptions { + abortOnError false + } + + aaptOptions { + noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS** + }**SIGN** + + buildTypes { + debug { + minifyEnabled **MINIFY_DEBUG** + useProguard **PROGUARD_DEBUG** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD** + jniDebuggable true + } + release { + minifyEnabled **MINIFY_RELEASE** + useProguard **PROGUARD_RELEASE** + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD****SIGNCONFIG** + } + }**PACKAGING_OPTIONS****SPLITS** +**BUILT_APK_LOCATION** +}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP** diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/settingsTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/settingsTemplate.gradle new file mode 100644 index 00000000..d23796f5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/settingsTemplate.gradle @@ -0,0 +1,36 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() +// Android Resolver Repos Start + def unityProjectPath = $/file:///**DIR_UNITYPROJECT**/$.replace("\\", "/") + maven { + url "file:///my/nonexistant/test/repo" // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/project_relative_path/repo") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17 + } + maven { + url (unityProjectPath + "/Assets/GeneratedLocalRepo/Firebase/m2repository") // Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10 + } + mavenLocal() +// Android Resolver Repos End + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/settingsTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/settingsTemplateDISABLED.gradle new file mode 100644 index 00000000..939fa3d5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate_2022_2/settingsTemplateDISABLED.gradle @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + **ARTIFACTORYREPOSITORY** + gradlePluginPortal() + google() + mavenCentral() + } +} + +include ':launcher', ':unityLibrary' +**INCLUDES** + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) + repositories { + **ARTIFACTORYREPOSITORY** + google() + mavenCentral() + flatDir { + dirs "${project(':unityLibrary').projectDir}/libs" + } + } +} diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.core.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.core.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.core.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.core.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.runtime-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.runtime-1.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.runtime-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.runtime-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-annotations-26.1.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-annotations-26.1.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-annotations-26.1.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-ui-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-ui-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-ui-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-ui-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-utils-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-utils-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-utils-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-utils-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-fragment-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-fragment-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-fragment-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-fragment-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-media-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-media-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-media-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-media-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-v4-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-v4-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-v4-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-v4-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-basement-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-basement-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-basement-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-basement-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-tasks-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-tasks-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-tasks-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-tasks-15.0.1.aar diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-app-unity-5.1.1.aar new file mode 100644 index 00000000..23794ccb Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/R.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/R.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/R.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/R.txt diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/libs/classes.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/libs/classes.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/libs/classes.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/libs/classes.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/proguard.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/proguard.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/proguard.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/proguard.txt diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/project.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/project.properties similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/project.properties rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/project.properties diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.core.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.core.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.core.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.core.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.common-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.common-1.0.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.common-1.0.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.common-1.0.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.runtime-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.runtime-1.0.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.runtime-1.0.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.runtime-1.0.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-annotations-26.1.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-annotations-26.1.0.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-annotations-26.1.0.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-annotations-26.1.0.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-ui-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-ui-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-ui-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-ui-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-utils-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-utils-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-utils-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-utils-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-fragment-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-fragment-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-fragment-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-fragment-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-media-compat-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-media-compat-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-media-compat-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-media-compat-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-v4-26.1.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-v4-26.1.0.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-v4-26.1.0.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-v4-26.1.0.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-basement-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-basement-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-basement-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-basement-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-tasks-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-tasks-15.0.1.aar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-tasks-15.0.1.aar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-tasks-15.0.1.aar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/AndroidManifest.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/AndroidManifest.xml similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/AndroidManifest.xml rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/AndroidManifest.xml diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/META-INF/MANIFEST.MF b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/META-INF/MANIFEST.MF new file mode 100644 index 00000000..cfe75e41 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Created-By: 1.8.0_221 (Oracle Corporation) + diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/R.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/R.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/R.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/R.txt diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/armeabi-v7a/libFirebaseCppApp-5.1.1.so b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/armeabi-v7a/libFirebaseCppApp-5.1.1.so similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/armeabi-v7a/libFirebaseCppApp-5.1.1.so rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/armeabi-v7a/libFirebaseCppApp-5.1.1.so diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/classes.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/classes.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/classes.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/classes.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/x86/libFirebaseCppApp-5.1.1.so b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/unsupported/libFirebaseCppApp-5.1.1.so similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/x86/libFirebaseCppApp-5.1.1.so rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/unsupported/libFirebaseCppApp-5.1.1.so diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/proguard.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/proguard.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/proguard.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/proguard.txt diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/project.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/project.properties similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/project.properties rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/project.properties diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/R.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/R.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/R.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/R.txt diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/libs/classes.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/libs/classes.jar similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/libs/classes.jar rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/libs/classes.jar diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/proguard.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/proguard.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/proguard.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/proguard.txt diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/project.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/project.properties similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/project.properties rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/project.properties diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt similarity index 100% rename from source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt rename to source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.annotation.annotation-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.annotation.annotation-1.0.0.jar new file mode 100644 index 00000000..124f128d Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.annotation.annotation-1.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.arch.core.core-common-2.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.arch.core.core-common-2.0.0.jar new file mode 100644 index 00000000..98ec8865 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.arch.core.core-common-2.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.arch.core.core-runtime-2.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.arch.core.core-runtime-2.0.0.aar new file mode 100644 index 00000000..f876595c Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.arch.core.core-runtime-2.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.asynclayoutinflater.asynclayoutinflater-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.asynclayoutinflater.asynclayoutinflater-1.0.0.aar new file mode 100644 index 00000000..337f4c49 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.asynclayoutinflater.asynclayoutinflater-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.collection.collection-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.collection.collection-1.0.0.jar new file mode 100644 index 00000000..78ac06c4 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.collection.collection-1.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.coordinatorlayout.coordinatorlayout-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.coordinatorlayout.coordinatorlayout-1.0.0.aar new file mode 100644 index 00000000..de447ec4 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.coordinatorlayout.coordinatorlayout-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.core.core-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.core.core-1.0.0.aar new file mode 100644 index 00000000..fea6bd3e Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.core.core-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.cursoradapter.cursoradapter-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.cursoradapter.cursoradapter-1.0.0.aar new file mode 100644 index 00000000..cd1494a9 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.cursoradapter.cursoradapter-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.customview.customview-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.customview.customview-1.0.0.aar new file mode 100644 index 00000000..73e70ac4 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.customview.customview-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.documentfile.documentfile-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.documentfile.documentfile-1.0.0.aar new file mode 100644 index 00000000..79fd5502 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.documentfile.documentfile-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.drawerlayout.drawerlayout-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.drawerlayout.drawerlayout-1.0.0.aar new file mode 100644 index 00000000..a9968c7f Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.drawerlayout.drawerlayout-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.fragment.fragment-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.fragment.fragment-1.0.0.aar new file mode 100644 index 00000000..7a5c3605 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.fragment.fragment-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.interpolator.interpolator-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.interpolator.interpolator-1.0.0.aar new file mode 100644 index 00000000..bccf86f7 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.interpolator.interpolator-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-core-ui-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-core-ui-1.0.0.aar new file mode 100644 index 00000000..01275eb2 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-core-ui-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-core-utils-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-core-utils-1.0.0.aar new file mode 100644 index 00000000..2980f603 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-core-utils-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-v4-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-v4-1.0.0.aar new file mode 100644 index 00000000..bc64a974 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.legacy.legacy-support-v4-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-common-2.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-common-2.0.0.jar new file mode 100644 index 00000000..6c3f095c Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-common-2.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-livedata-2.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-livedata-2.0.0.aar new file mode 100644 index 00000000..27b091c1 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-livedata-2.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-livedata-core-2.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-livedata-core-2.0.0.aar new file mode 100644 index 00000000..5583b9f5 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-livedata-core-2.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-runtime-2.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-runtime-2.0.0.aar new file mode 100644 index 00000000..0809d720 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-runtime-2.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-viewmodel-2.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-viewmodel-2.0.0.aar new file mode 100644 index 00000000..b142a708 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.lifecycle.lifecycle-viewmodel-2.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.loader.loader-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.loader.loader-1.0.0.aar new file mode 100644 index 00000000..32c57746 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.loader.loader-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.localbroadcastmanager.localbroadcastmanager-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.localbroadcastmanager.localbroadcastmanager-1.0.0.aar new file mode 100644 index 00000000..e9074ee4 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.localbroadcastmanager.localbroadcastmanager-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.media.media-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.media.media-1.0.0.aar new file mode 100644 index 00000000..c07fcaeb Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.media.media-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.print.print-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.print.print-1.0.0.aar new file mode 100644 index 00000000..7bb51fd5 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.print.print-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.slidingpanelayout.slidingpanelayout-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.slidingpanelayout.slidingpanelayout-1.0.0.aar new file mode 100644 index 00000000..ebee0eee Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.slidingpanelayout.slidingpanelayout-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.swiperefreshlayout.swiperefreshlayout-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.swiperefreshlayout.swiperefreshlayout-1.0.0.aar new file mode 100644 index 00000000..71d4748e Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.swiperefreshlayout.swiperefreshlayout-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.versionedparcelable.versionedparcelable-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.versionedparcelable.versionedparcelable-1.0.0.aar new file mode 100644 index 00000000..5cf661c3 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.versionedparcelable.versionedparcelable-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.viewpager.viewpager-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.viewpager.viewpager-1.0.0.aar new file mode 100644 index 00000000..a7667298 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/androidx.viewpager.viewpager-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.android.gms.play-services-basement-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.android.gms.play-services-basement-15.0.1.aar new file mode 100644 index 00000000..552cb66e Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.android.gms.play-services-basement-15.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.android.gms.play-services-tasks-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.android.gms.play-services-tasks-15.0.1.aar new file mode 100644 index 00000000..c8c9eb4a Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.android.gms.play-services-tasks-15.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/AndroidManifest.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/AndroidManifest.xml new file mode 100644 index 00000000..2449f75e --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/META-INF/MANIFEST.MF b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/META-INF/MANIFEST.MF new file mode 100644 index 00000000..cfe75e41 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Created-By: 1.8.0_221 (Oracle Corporation) + diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/R.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/R.txt new file mode 100644 index 00000000..e69de29b diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/armeabi-v7a/libFirebaseCppApp-5.1.1.so b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/armeabi-v7a/libFirebaseCppApp-5.1.1.so new file mode 100644 index 00000000..18640013 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/armeabi-v7a/libFirebaseCppApp-5.1.1.so differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/classes.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/classes.jar new file mode 100644 index 00000000..15cb0ecb Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/classes.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/unsupported/libFirebaseCppApp-5.1.1.so b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/unsupported/libFirebaseCppApp-5.1.1.so new file mode 100644 index 00000000..e7c246c1 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/libs/unsupported/libFirebaseCppApp-5.1.1.so differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/proguard.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/proguard.txt new file mode 100644 index 00000000..64171024 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/proguard.txt @@ -0,0 +1,10 @@ +-keep,includedescriptorclasses public class com.google.android.gms.common.GoogleApiAvailability{ *; } +-keep,includedescriptorclasses public class com.google.android.gms.crash.internal.api.CrashApiImpl { *; } +-keep,includedescriptorclasses public class com.google.android.gms.tasks.OnFailureListener { *; } +-keep,includedescriptorclasses public class com.google.android.gms.tasks.OnSuccessListener { *; } +-keep,includedescriptorclasses public class com.google.android.gms.tasks.Task { *; } +-keep,includedescriptorclasses public class com.google.firebase.FirebaseApp{ *; } +-keep,includedescriptorclasses public class com.google.firebase.FirebaseOptions{ *; } +-keep,includedescriptorclasses public class com.google.firebase.FirebaseOptions$Builder{ *; } +-keep,includedescriptorclasses public class com.google.firebase.iid.FirebaseInstanceId{ *; } +-keep,includedescriptorclasses public class dalvik.system.DexClassLoader{ *; } diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/project.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/project.properties new file mode 100644 index 00000000..d28e5065 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-app-unity-5.1.1/project.properties @@ -0,0 +1,3 @@ +# Project target. +target=android-9 +android.library=true diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml new file mode 100644 index 00000000..b53feb36 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/R.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/R.txt new file mode 100644 index 00000000..d80f0c58 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/R.txt @@ -0,0 +1,122 @@ +int attr font 0x7f040001 +int attr fontProviderAuthority 0x7f040002 +int attr fontProviderCerts 0x7f040003 +int attr fontProviderFetchStrategy 0x7f040004 +int attr fontProviderFetchTimeout 0x7f040005 +int attr fontProviderPackage 0x7f040006 +int attr fontProviderQuery 0x7f040007 +int attr fontStyle 0x7f040008 +int attr fontWeight 0x7f040009 +int bool abc_action_bar_embed_tabs 0x7f050001 +int color notification_action_color_filter 0x7f060001 +int color notification_icon_bg_color 0x7f060002 +int color notification_material_background_media_default_color 0x7f060003 +int color primary_text_default_material_dark 0x7f060004 +int color ripple_material_light 0x7f060005 +int color secondary_text_default_material_dark 0x7f060006 +int color secondary_text_default_material_light 0x7f060007 +int dimen compat_button_inset_horizontal_material 0x7f080001 +int dimen compat_button_inset_vertical_material 0x7f080002 +int dimen compat_button_padding_horizontal_material 0x7f080003 +int dimen compat_button_padding_vertical_material 0x7f080004 +int dimen compat_control_corner_material 0x7f080005 +int dimen notification_action_icon_size 0x7f080006 +int dimen notification_action_text_size 0x7f080007 +int dimen notification_big_circle_margin 0x7f080008 +int dimen notification_content_margin_start 0x7f080009 +int dimen notification_large_icon_height 0x7f08000a +int dimen notification_large_icon_width 0x7f08000b +int dimen notification_main_column_padding_top 0x7f08000c +int dimen notification_media_narrow_margin 0x7f08000d +int dimen notification_right_icon_size 0x7f08000e +int dimen notification_right_side_padding_top 0x7f08000f +int dimen notification_small_icon_background_padding 0x7f080010 +int dimen notification_small_icon_size_as_large 0x7f080011 +int dimen notification_subtext_size 0x7f080012 +int dimen notification_top_pad 0x7f080013 +int dimen notification_top_pad_large_text 0x7f080014 +int drawable notification_action_background 0x7f090001 +int drawable notification_bg 0x7f090002 +int drawable notification_bg_low 0x7f090003 +int drawable notification_bg_low_normal 0x7f090004 +int drawable notification_bg_low_pressed 0x7f090005 +int drawable notification_bg_normal 0x7f090006 +int drawable notification_bg_normal_pressed 0x7f090007 +int drawable notification_icon_background 0x7f090008 +int drawable notification_template_icon_bg 0x7f090009 +int drawable notification_template_icon_low_bg 0x7f09000a +int drawable notification_tile_bg 0x7f09000b +int drawable notify_panel_notification_icon_bg 0x7f09000c +int id action0 0x7f0c0001 +int id action_container 0x7f0c0002 +int id action_divider 0x7f0c0003 +int id action_image 0x7f0c0004 +int id action_text 0x7f0c0005 +int id actions 0x7f0c0006 +int id async 0x7f0c0007 +int id blocking 0x7f0c0008 +int id cancel_action 0x7f0c0009 +int id chronometer 0x7f0c000a +int id end_padder 0x7f0c000b +int id forever 0x7f0c000c +int id icon 0x7f0c000d +int id icon_group 0x7f0c000e +int id info 0x7f0c000f +int id italic 0x7f0c0010 +int id line1 0x7f0c0011 +int id line3 0x7f0c0012 +int id media_actions 0x7f0c0013 +int id normal 0x7f0c0014 +int id notification_background 0x7f0c0015 +int id notification_main_column 0x7f0c0016 +int id notification_main_column_container 0x7f0c0017 +int id right_icon 0x7f0c0018 +int id right_side 0x7f0c0019 +int id status_bar_latest_event_content 0x7f0c001a +int id text 0x7f0c001b +int id text2 0x7f0c001c +int id time 0x7f0c001d +int id title 0x7f0c001e +int integer cancel_button_image_alpha 0x7f0d0001 +int integer google_play_services_version 0x7f0d0002 +int integer status_bar_notification_info_maxnum 0x7f0d0003 +int layout notification_action 0x7f0f0001 +int layout notification_action_tombstone 0x7f0f0002 +int layout notification_media_action 0x7f0f0003 +int layout notification_media_cancel_action 0x7f0f0004 +int layout notification_template_big_media 0x7f0f0005 +int layout notification_template_big_media_custom 0x7f0f0006 +int layout notification_template_big_media_narrow 0x7f0f0007 +int layout notification_template_big_media_narrow_custom 0x7f0f0008 +int layout notification_template_custom_big 0x7f0f0009 +int layout notification_template_icon_group 0x7f0f000a +int layout notification_template_lines_media 0x7f0f000b +int layout notification_template_media 0x7f0f000c +int layout notification_template_media_custom 0x7f0f000d +int layout notification_template_part_chronometer 0x7f0f000e +int layout notification_template_part_time 0x7f0f000f +int string common_google_play_services_unknown_issue 0x7f150001 +int string status_bar_notification_info_overflow 0x7f150002 +int style TextAppearance_Compat_Notification 0x7f160001 +int style TextAppearance_Compat_Notification_Info 0x7f160002 +int style TextAppearance_Compat_Notification_Info_Media 0x7f160003 +int style TextAppearance_Compat_Notification_Line2 0x7f160004 +int style TextAppearance_Compat_Notification_Line2_Media 0x7f160005 +int style TextAppearance_Compat_Notification_Media 0x7f160006 +int style TextAppearance_Compat_Notification_Time 0x7f160007 +int style TextAppearance_Compat_Notification_Time_Media 0x7f160008 +int style TextAppearance_Compat_Notification_Title 0x7f160009 +int style TextAppearance_Compat_Notification_Title_Media 0x7f16000a +int style Widget_Compat_NotificationActionContainer 0x7f16000b +int style Widget_Compat_NotificationActionText 0x7f16000c +int[] styleable FontFamily { 0x7f040002, 0x7f040003, 0x7f040004, 0x7f040005, 0x7f040006, 0x7f040007 } +int styleable FontFamily_fontProviderAuthority 0 +int styleable FontFamily_fontProviderCerts 1 +int styleable FontFamily_fontProviderFetchStrategy 2 +int styleable FontFamily_fontProviderFetchTimeout 3 +int styleable FontFamily_fontProviderPackage 4 +int styleable FontFamily_fontProviderQuery 5 +int[] styleable FontFamilyFont { 0x7f040001, 0x7f040008, 0x7f040009 } +int styleable FontFamilyFont_font 0 +int styleable FontFamilyFont_fontStyle 1 +int styleable FontFamilyFont_fontWeight 2 diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/libs/classes.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/libs/classes.jar new file mode 100644 index 00000000..c8312dc8 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/libs/classes.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/proguard.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/proguard.txt new file mode 100644 index 00000000..63c0883b --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/proguard.txt @@ -0,0 +1 @@ +-dontwarn com.google.firebase.components.Component$Instantiation \ No newline at end of file diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/project.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/project.properties new file mode 100644 index 00000000..d28e5065 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/project.properties @@ -0,0 +1,3 @@ +# Project target. +target=android-9 +android.library=true diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json new file mode 100644 index 00000000..0f8c2436 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json @@ -0,0 +1 @@ +{"android arch-common":{"length":11361,"start":20},"android lifecycle runtime":{"length":11361,"start":11407},"android lifecycle-common":{"length":11361,"start":22793},"android support library annotations":{"length":11361,"start":34190},"android support library compat":{"length":11361,"start":45582},"android support library core ui":{"length":11361,"start":56975},"android support library core utils":{"length":11361,"start":68371},"android support library fragment":{"length":11361,"start":79765},"android support library media compat":{"length":11361,"start":91163},"android support library v4":{"length":11361,"start":102551},"play-services-basement":{"length":3,"start":113935},"play-services-tasks":{"length":3,"start":113958},"CCTZ":{"length":11360,"start":113966},"ICU4C":{"length":19443,"start":125332},"JSR 305":{"length":1588,"start":144783},"PCRE":{"length":3184,"start":146376},"Protobuf Nano":{"length":1734,"start":149574},"RE2":{"length":1560,"start":151312},"STL":{"length":682,"start":152876},"UTF":{"length":733,"start":153562},"darts_clone":{"length":1481,"start":154307},"flatbuffers":{"length":11360,"start":155800},"safeparcel":{"length":11360,"start":167171},"zlib":{"length":2502,"start":178536}} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt new file mode 100644 index 00000000..7302c6e2 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt @@ -0,0 +1,3342 @@ +Android Arch-Common: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Lifecycle Runtime: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Lifecycle-Common: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Support Library Annotations: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Support Library compat: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Support Library core UI: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Support Library core utils: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Support Library fragment: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Support Library media compat: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Support Library v4: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Play-services-basement: + + +Play-services-tasks: + + +CCTZ: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +ICU4C: +COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later) + +Copyright © 1991-2017 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in http://www.unicode.org/copyright.html + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder. + +--------------------- + +Third-Party Software Licenses + +This section contains third-party software notices and/or additional +terms for licensed third-party software components included within ICU +libraries. + +1. ICU License - ICU 1.8.1 to ICU 57.1 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1995-2016 International Business Machines Corporation and others +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, and/or sell copies of the Software, and to permit persons +to whom the Software is furnished to do so, provided that the above +copyright notice(s) and this permission notice appear in all copies of +the Software and that both the above copyright notice(s) and this +permission notice appear in supporting documentation. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY +SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, use +or other dealings in this Software without prior written authorization +of the copyright holder. + +All trademarks and registered trademarks mentioned herein are the +property of their respective owners. + +2. Chinese/Japanese Word Break Dictionary Data (cjdict.txt) + + # The Google Chrome software developed by Google is licensed under + # the BSD license. Other software included in this distribution is + # provided under other licenses, as set forth below. + # + # The BSD License + # http://opensource.org/licenses/bsd-license.php + # Copyright (C) 2006-2008, Google Inc. + # + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions are met: + # + # Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # Redistributions in binary form must reproduce the above + # copyright notice, this list of conditions and the following + # disclaimer in the documentation and/or other materials provided with + # the distribution. + # Neither the name of Google Inc. nor the names of its + # contributors may be used to endorse or promote products derived from + # this software without specific prior written permission. + # + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + # + # + # The word list in cjdict.txt are generated by combining three word lists + # listed below with further processing for compound word breaking. The + # frequency is generated with an iterative training against Google web + # corpora. + # + # * Libtabe (Chinese) + # - https://sourceforge.net/project/?group_id=1519 + # - Its license terms and conditions are shown below. + # + # * IPADIC (Japanese) + # - http://chasen.aist-nara.ac.jp/chasen/distribution.html + # - Its license terms and conditions are shown below. + # + # ---------COPYING.libtabe ---- BEGIN-------------------- + # + # /* + # * Copyright (c) 1999 TaBE Project. + # * Copyright (c) 1999 Pai-Hsiang Hsiao. + # * All rights reserved. + # * + # * Redistribution and use in source and binary forms, with or without + # * modification, are permitted provided that the following conditions + # * are met: + # * + # * . Redistributions of source code must retain the above copyright + # * notice, this list of conditions and the following disclaimer. + # * . Redistributions in binary form must reproduce the above copyright + # * notice, this list of conditions and the following disclaimer in + # * the documentation and/or other materials provided with the + # * distribution. + # * . Neither the name of the TaBE Project nor the names of its + # * contributors may be used to endorse or promote products derived + # * from this software without specific prior written permission. + # * + # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # * OF THE POSSIBILITY OF SUCH DAMAGE. + # */ + # + # /* + # * Copyright (c) 1999 Computer Systems and Communication Lab, + # * Institute of Information Science, Academia + # * Sinica. All rights reserved. + # * + # * Redistribution and use in source and binary forms, with or without + # * modification, are permitted provided that the following conditions + # * are met: + # * + # * . Redistributions of source code must retain the above copyright + # * notice, this list of conditions and the following disclaimer. + # * . Redistributions in binary form must reproduce the above copyright + # * notice, this list of conditions and the following disclaimer in + # * the documentation and/or other materials provided with the + # * distribution. + # * . Neither the name of the Computer Systems and Communication Lab + # * nor the names of its contributors may be used to endorse or + # * promote products derived from this software without specific + # * prior written permission. + # * + # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # * OF THE POSSIBILITY OF SUCH DAMAGE. + # */ + # + # Copyright 1996 Chih-Hao Tsai @ Beckman Institute, + # University of Illinois + # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4 + # + # ---------------COPYING.libtabe-----END-------------------------------- + # + # + # ---------------COPYING.ipadic-----BEGIN------------------------------- + # + # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science + # and Technology. All Rights Reserved. + # + # Use, reproduction, and distribution of this software is permitted. + # Any copy of this software, whether in its original form or modified, + # must include both the above copyright notice and the following + # paragraphs. + # + # Nara Institute of Science and Technology (NAIST), + # the copyright holders, disclaims all warranties with regard to this + # software, including all implied warranties of merchantability and + # fitness, in no event shall NAIST be liable for + # any special, indirect or consequential damages or any damages + # whatsoever resulting from loss of use, data or profits, whether in an + # action of contract, negligence or other tortuous action, arising out + # of or in connection with the use or performance of this software. + # + # A large portion of the dictionary entries + # originate from ICOT Free Software. The following conditions for ICOT + # Free Software applies to the current dictionary as well. + # + # Each User may also freely distribute the Program, whether in its + # original form or modified, to any third party or parties, PROVIDED + # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear + # on, or be attached to, the Program, which is distributed substantially + # in the same form as set out herein and that such intended + # distribution, if actually made, will neither violate or otherwise + # contravene any of the laws and regulations of the countries having + # jurisdiction over the User or the intended distribution itself. + # + # NO WARRANTY + # + # The program was produced on an experimental basis in the course of the + # research and development conducted during the project and is provided + # to users as so produced on an experimental basis. Accordingly, the + # program is provided without any warranty whatsoever, whether express, + # implied, statutory or otherwise. The term "warranty" used herein + # includes, but is not limited to, any warranty of the quality, + # performance, merchantability and fitness for a particular purpose of + # the program and the nonexistence of any infringement or violation of + # any right of any third party. + # + # Each user of the program will agree and understand, and be deemed to + # have agreed and understood, that there is no warranty whatsoever for + # the program and, accordingly, the entire risk arising from or + # otherwise connected with the program is assumed by the user. + # + # Therefore, neither ICOT, the copyright holder, or any other + # organization that participated in or was otherwise related to the + # development of the program and their respective officials, directors, + # officers and other employees shall be held liable for any and all + # damages, including, without limitation, general, special, incidental + # and consequential damages, arising out of or otherwise in connection + # with the use or inability to use the program or any product, material + # or result produced or otherwise obtained by using the program, + # regardless of whether they have been advised of, or otherwise had + # knowledge of, the possibility of such damages at any time during the + # project or thereafter. Each user will be deemed to have agreed to the + # foregoing by his or her commencement of use of the program. The term + # "use" as used herein includes, but is not limited to, the use, + # modification, copying and distribution of the program and the + # production of secondary products from the program. + # + # In the case where the program, whether in its original form or + # modified, was distributed or delivered to or received by a user from + # any person, organization or entity other than ICOT, unless it makes or + # grants independently of ICOT any specific warranty to the user in + # writing, such person, organization or entity, will also be exempted + # from and not be held liable to the user for any such damages as noted + # above as far as the program is concerned. + # + # ---------------COPYING.ipadic-----END---------------------------------- + +3. Lao Word Break Dictionary Data (laodict.txt) + + # Copyright (c) 2013 International Business Machines Corporation + # and others. All Rights Reserved. + # + # Project: http://code.google.com/p/lao-dictionary/ + # Dictionary: http://lao-dictionary.googlecode.com/git/Lao-Dictionary.txt + # License: http://lao-dictionary.googlecode.com/git/Lao-Dictionary-LICENSE.txt + # (copied below) + # + # This file is derived from the above dictionary, with slight + # modifications. + # ---------------------------------------------------------------------- + # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell. + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, + # are permitted provided that the following conditions are met: + # + # + # Redistributions of source code must retain the above copyright notice, this + # list of conditions and the following disclaimer. Redistributions in + # binary form must reproduce the above copyright notice, this list of + # conditions and the following disclaimer in the documentation and/or + # other materials provided with the distribution. + # + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # OF THE POSSIBILITY OF SUCH DAMAGE. + # -------------------------------------------------------------------------- + +4. Burmese Word Break Dictionary Data (burmesedict.txt) + + # Copyright (c) 2014 International Business Machines Corporation + # and others. All Rights Reserved. + # + # This list is part of a project hosted at: + # github.com/kanyawtech/myanmar-karen-word-lists + # + # -------------------------------------------------------------------------- + # Copyright (c) 2013, LeRoy Benjamin Sharon + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: Redistributions of source code must retain the above + # copyright notice, this list of conditions and the following + # disclaimer. Redistributions in binary form must reproduce the + # above copyright notice, this list of conditions and the following + # disclaimer in the documentation and/or other materials provided + # with the distribution. + # + # Neither the name Myanmar Karen Word Lists, nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + # SUCH DAMAGE. + # -------------------------------------------------------------------------- + +5. Time Zone Database + + ICU uses the public domain data and code derived from Time Zone +Database for its time zone support. The ownership of the TZ database +is explained in BCP 175: Procedure for Maintaining the Time Zone +Database section 7. + + # 7. Database Ownership + # + # The TZ database itself is not an IETF Contribution or an IETF + # document. Rather it is a pre-existing and regularly updated work + # that is in the public domain, and is intended to remain in the + # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do + # not apply to the TZ Database or contributions that individuals make + # to it. Should any claims be made and substantiated against the TZ + # Database, the organization that is providing the IANA + # Considerations defined in this RFC, under the memorandum of + # understanding with the IETF, currently ICANN, may act in accordance + # with all competent court orders. No ownership claims will be made + # by ICANN or the IETF Trust on the database or the code. Any person + # making a contribution to the database or code waives all rights to + # future claims in that contribution or in the TZ Database. + +JSR 305: +Copyright (c) 2007-2009, JSR305 expert group +All rights reserved. + +http://www.opensource.org/licenses/bsd-license.php + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the JSR305 expert group nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +PCRE: +PCRE LICENCE +------------ + +PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + +Release 8 of PCRE is distributed under the terms of the "BSD" licence, as +specified below. The documentation for PCRE, supplied in the "doc" +directory, is distributed under the same terms as the software itself. The data +in the testdata directory is not copyrighted and is in the public domain. + +The basic library functions are written in C and are freestanding. Also +included in the distribution is a set of C++ wrapper functions, and a +just-in-time compiler that can be used to optimize pattern matching. These +are both optional features that can be omitted when the library is built. + + +THE BASIC LIBRARY FUNCTIONS +--------------------------- + +Written by: Philip Hazel +Email local part: ph10 +Email domain: cam.ac.uk + +University of Cambridge Computing Service, +Cambridge, England. + +Copyright (c) 1997-2017 University of Cambridge +All rights reserved. + + +PCRE JUST-IN-TIME COMPILATION SUPPORT +------------------------------------- + +Written by: Zoltan Herczeg +Email local part: hzmester +Emain domain: freemail.hu + +Copyright(c) 2010-2017 Zoltan Herczeg +All rights reserved. + + +STACK-LESS JUST-IN-TIME COMPILER +-------------------------------- + +Written by: Zoltan Herczeg +Email local part: hzmester +Emain domain: freemail.hu + +Copyright(c) 2009-2017 Zoltan Herczeg +All rights reserved. + + +THE C++ WRAPPER FUNCTIONS +------------------------- + +Contributed by: Google Inc. + +Copyright (c) 2007-2012, Google Inc. +All rights reserved. + + +THE "BSD" LICENCE +----------------- + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the name of Google + Inc. nor the names of their contributors may be used to endorse or + promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +End + +Protobuf Nano: +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Code generated by the Protocol Buffer compiler is owned by the owner +of the input file used when generating it. This code is not +standalone and requires a support library to be linked with it. This +support library is itself covered by the above license. + +RE2: +// Copyright (c) 2009 The RE2 Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +STL: +SGI STL + +The STL portion of GNU libstdc++ that is used with gcc3 and gcc4 is licensed +under the GPL, with the following exception: + +# As a special exception, you may use this file as part of a free software +# library without restriction. Specifically, if other files instantiate +# templates or use macros or inline functions from this file, or you compile +# this file and link it with other files to produce an executable, this +# file does not by itself cause the resulting executable to be covered by +# the GNU General Public License. This exception does not however +# invalidate any other reasons why the executable file might be covered by +# the GNU General Public License. + + +UTF: +UTF-8 Library + +The authors of this software are Rob Pike and Ken Thompson. + Copyright (c) 1998-2002 by Lucent Technologies. +Permission to use, copy, modify, and distribute this software for any +purpose without fee is hereby granted, provided that this entire notice +is included in all copies of any software which is or includes a copy +or modification of this software and in all copies of the supporting +documentation for such software. +THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY +REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY +OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + +darts_clone: +Copyright (c) 2008-2011, Susumu Yata +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +- Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +flatbuffers: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +safeparcel: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +zlib: +(extracted from README, except for match.S) + +Copyright notice: + + (C) 1995-2013 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +If you use the zlib library in a product, we would appreciate *not* receiving +lengthy legal documents to sign. The sources are provided for free but without +warranty of any kind. The library has been entirely written by Jean-loup +Gailly and Mark Adler; it does not include third-party code. + +If you redistribute modified sources, we would appreciate that you include in +the file ChangeLog history information documenting your changes. Please read +the FAQ for more information on the distribution of modified source versions. + +(extracted from match.S, for match.S only) + +Copyright (C) 1998, 2007 Brian Raiter + +This software is provided 'as-is', without any express or implied +warranty. In no event will the author be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.annotation.annotation-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.annotation.annotation-1.0.0.jar new file mode 100644 index 00000000..124f128d Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.annotation.annotation-1.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.arch.core.core-common-2.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.arch.core.core-common-2.0.0.jar new file mode 100644 index 00000000..98ec8865 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.arch.core.core-common-2.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.arch.core.core-runtime-2.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.arch.core.core-runtime-2.0.0.aar new file mode 100644 index 00000000..f876595c Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.arch.core.core-runtime-2.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.asynclayoutinflater.asynclayoutinflater-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.asynclayoutinflater.asynclayoutinflater-1.0.0.aar new file mode 100644 index 00000000..337f4c49 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.asynclayoutinflater.asynclayoutinflater-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.collection.collection-1.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.collection.collection-1.0.0.jar new file mode 100644 index 00000000..78ac06c4 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.collection.collection-1.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.coordinatorlayout.coordinatorlayout-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.coordinatorlayout.coordinatorlayout-1.0.0.aar new file mode 100644 index 00000000..de447ec4 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.coordinatorlayout.coordinatorlayout-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.core.core-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.core.core-1.0.0.aar new file mode 100644 index 00000000..fea6bd3e Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.core.core-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.cursoradapter.cursoradapter-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.cursoradapter.cursoradapter-1.0.0.aar new file mode 100644 index 00000000..cd1494a9 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.cursoradapter.cursoradapter-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.customview.customview-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.customview.customview-1.0.0.aar new file mode 100644 index 00000000..73e70ac4 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.customview.customview-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.documentfile.documentfile-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.documentfile.documentfile-1.0.0.aar new file mode 100644 index 00000000..79fd5502 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.documentfile.documentfile-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.drawerlayout.drawerlayout-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.drawerlayout.drawerlayout-1.0.0.aar new file mode 100644 index 00000000..a9968c7f Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.drawerlayout.drawerlayout-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.fragment.fragment-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.fragment.fragment-1.0.0.aar new file mode 100644 index 00000000..7a5c3605 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.fragment.fragment-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.interpolator.interpolator-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.interpolator.interpolator-1.0.0.aar new file mode 100644 index 00000000..bccf86f7 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.interpolator.interpolator-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-core-ui-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-core-ui-1.0.0.aar new file mode 100644 index 00000000..01275eb2 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-core-ui-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-core-utils-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-core-utils-1.0.0.aar new file mode 100644 index 00000000..2980f603 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-core-utils-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-v4-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-v4-1.0.0.aar new file mode 100644 index 00000000..bc64a974 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.legacy.legacy-support-v4-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-common-2.0.0.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-common-2.0.0.jar new file mode 100644 index 00000000..6c3f095c Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-common-2.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-livedata-2.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-livedata-2.0.0.aar new file mode 100644 index 00000000..27b091c1 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-livedata-2.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-livedata-core-2.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-livedata-core-2.0.0.aar new file mode 100644 index 00000000..5583b9f5 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-livedata-core-2.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-runtime-2.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-runtime-2.0.0.aar new file mode 100644 index 00000000..0809d720 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-runtime-2.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-viewmodel-2.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-viewmodel-2.0.0.aar new file mode 100644 index 00000000..b142a708 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.lifecycle.lifecycle-viewmodel-2.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.loader.loader-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.loader.loader-1.0.0.aar new file mode 100644 index 00000000..32c57746 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.loader.loader-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.localbroadcastmanager.localbroadcastmanager-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.localbroadcastmanager.localbroadcastmanager-1.0.0.aar new file mode 100644 index 00000000..e9074ee4 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.localbroadcastmanager.localbroadcastmanager-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.media.media-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.media.media-1.0.0.aar new file mode 100644 index 00000000..c07fcaeb Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.media.media-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.print.print-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.print.print-1.0.0.aar new file mode 100644 index 00000000..7bb51fd5 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.print.print-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.slidingpanelayout.slidingpanelayout-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.slidingpanelayout.slidingpanelayout-1.0.0.aar new file mode 100644 index 00000000..ebee0eee Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.slidingpanelayout.slidingpanelayout-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.swiperefreshlayout.swiperefreshlayout-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.swiperefreshlayout.swiperefreshlayout-1.0.0.aar new file mode 100644 index 00000000..71d4748e Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.swiperefreshlayout.swiperefreshlayout-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.versionedparcelable.versionedparcelable-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.versionedparcelable.versionedparcelable-1.0.0.aar new file mode 100644 index 00000000..5cf661c3 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.versionedparcelable.versionedparcelable-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.viewpager.viewpager-1.0.0.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.viewpager.viewpager-1.0.0.aar new file mode 100644 index 00000000..a7667298 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/androidx.viewpager.viewpager-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.android.gms.play-services-basement-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.android.gms.play-services-basement-15.0.1.aar new file mode 100644 index 00000000..318e7fcf Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.android.gms.play-services-basement-15.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.android.gms.play-services-tasks-15.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.android.gms.play-services-tasks-15.0.1.aar new file mode 100644 index 00000000..82803e2f Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.android.gms.play-services-tasks-15.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-app-unity-5.1.1.aar new file mode 100644 index 00000000..23794ccb Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml new file mode 100644 index 00000000..b53feb36 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/R.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/R.txt new file mode 100644 index 00000000..d80f0c58 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/R.txt @@ -0,0 +1,122 @@ +int attr font 0x7f040001 +int attr fontProviderAuthority 0x7f040002 +int attr fontProviderCerts 0x7f040003 +int attr fontProviderFetchStrategy 0x7f040004 +int attr fontProviderFetchTimeout 0x7f040005 +int attr fontProviderPackage 0x7f040006 +int attr fontProviderQuery 0x7f040007 +int attr fontStyle 0x7f040008 +int attr fontWeight 0x7f040009 +int bool abc_action_bar_embed_tabs 0x7f050001 +int color notification_action_color_filter 0x7f060001 +int color notification_icon_bg_color 0x7f060002 +int color notification_material_background_media_default_color 0x7f060003 +int color primary_text_default_material_dark 0x7f060004 +int color ripple_material_light 0x7f060005 +int color secondary_text_default_material_dark 0x7f060006 +int color secondary_text_default_material_light 0x7f060007 +int dimen compat_button_inset_horizontal_material 0x7f080001 +int dimen compat_button_inset_vertical_material 0x7f080002 +int dimen compat_button_padding_horizontal_material 0x7f080003 +int dimen compat_button_padding_vertical_material 0x7f080004 +int dimen compat_control_corner_material 0x7f080005 +int dimen notification_action_icon_size 0x7f080006 +int dimen notification_action_text_size 0x7f080007 +int dimen notification_big_circle_margin 0x7f080008 +int dimen notification_content_margin_start 0x7f080009 +int dimen notification_large_icon_height 0x7f08000a +int dimen notification_large_icon_width 0x7f08000b +int dimen notification_main_column_padding_top 0x7f08000c +int dimen notification_media_narrow_margin 0x7f08000d +int dimen notification_right_icon_size 0x7f08000e +int dimen notification_right_side_padding_top 0x7f08000f +int dimen notification_small_icon_background_padding 0x7f080010 +int dimen notification_small_icon_size_as_large 0x7f080011 +int dimen notification_subtext_size 0x7f080012 +int dimen notification_top_pad 0x7f080013 +int dimen notification_top_pad_large_text 0x7f080014 +int drawable notification_action_background 0x7f090001 +int drawable notification_bg 0x7f090002 +int drawable notification_bg_low 0x7f090003 +int drawable notification_bg_low_normal 0x7f090004 +int drawable notification_bg_low_pressed 0x7f090005 +int drawable notification_bg_normal 0x7f090006 +int drawable notification_bg_normal_pressed 0x7f090007 +int drawable notification_icon_background 0x7f090008 +int drawable notification_template_icon_bg 0x7f090009 +int drawable notification_template_icon_low_bg 0x7f09000a +int drawable notification_tile_bg 0x7f09000b +int drawable notify_panel_notification_icon_bg 0x7f09000c +int id action0 0x7f0c0001 +int id action_container 0x7f0c0002 +int id action_divider 0x7f0c0003 +int id action_image 0x7f0c0004 +int id action_text 0x7f0c0005 +int id actions 0x7f0c0006 +int id async 0x7f0c0007 +int id blocking 0x7f0c0008 +int id cancel_action 0x7f0c0009 +int id chronometer 0x7f0c000a +int id end_padder 0x7f0c000b +int id forever 0x7f0c000c +int id icon 0x7f0c000d +int id icon_group 0x7f0c000e +int id info 0x7f0c000f +int id italic 0x7f0c0010 +int id line1 0x7f0c0011 +int id line3 0x7f0c0012 +int id media_actions 0x7f0c0013 +int id normal 0x7f0c0014 +int id notification_background 0x7f0c0015 +int id notification_main_column 0x7f0c0016 +int id notification_main_column_container 0x7f0c0017 +int id right_icon 0x7f0c0018 +int id right_side 0x7f0c0019 +int id status_bar_latest_event_content 0x7f0c001a +int id text 0x7f0c001b +int id text2 0x7f0c001c +int id time 0x7f0c001d +int id title 0x7f0c001e +int integer cancel_button_image_alpha 0x7f0d0001 +int integer google_play_services_version 0x7f0d0002 +int integer status_bar_notification_info_maxnum 0x7f0d0003 +int layout notification_action 0x7f0f0001 +int layout notification_action_tombstone 0x7f0f0002 +int layout notification_media_action 0x7f0f0003 +int layout notification_media_cancel_action 0x7f0f0004 +int layout notification_template_big_media 0x7f0f0005 +int layout notification_template_big_media_custom 0x7f0f0006 +int layout notification_template_big_media_narrow 0x7f0f0007 +int layout notification_template_big_media_narrow_custom 0x7f0f0008 +int layout notification_template_custom_big 0x7f0f0009 +int layout notification_template_icon_group 0x7f0f000a +int layout notification_template_lines_media 0x7f0f000b +int layout notification_template_media 0x7f0f000c +int layout notification_template_media_custom 0x7f0f000d +int layout notification_template_part_chronometer 0x7f0f000e +int layout notification_template_part_time 0x7f0f000f +int string common_google_play_services_unknown_issue 0x7f150001 +int string status_bar_notification_info_overflow 0x7f150002 +int style TextAppearance_Compat_Notification 0x7f160001 +int style TextAppearance_Compat_Notification_Info 0x7f160002 +int style TextAppearance_Compat_Notification_Info_Media 0x7f160003 +int style TextAppearance_Compat_Notification_Line2 0x7f160004 +int style TextAppearance_Compat_Notification_Line2_Media 0x7f160005 +int style TextAppearance_Compat_Notification_Media 0x7f160006 +int style TextAppearance_Compat_Notification_Time 0x7f160007 +int style TextAppearance_Compat_Notification_Time_Media 0x7f160008 +int style TextAppearance_Compat_Notification_Title 0x7f160009 +int style TextAppearance_Compat_Notification_Title_Media 0x7f16000a +int style Widget_Compat_NotificationActionContainer 0x7f16000b +int style Widget_Compat_NotificationActionText 0x7f16000c +int[] styleable FontFamily { 0x7f040002, 0x7f040003, 0x7f040004, 0x7f040005, 0x7f040006, 0x7f040007 } +int styleable FontFamily_fontProviderAuthority 0 +int styleable FontFamily_fontProviderCerts 1 +int styleable FontFamily_fontProviderFetchStrategy 2 +int styleable FontFamily_fontProviderFetchTimeout 3 +int styleable FontFamily_fontProviderPackage 4 +int styleable FontFamily_fontProviderQuery 5 +int[] styleable FontFamilyFont { 0x7f040001, 0x7f040008, 0x7f040009 } +int styleable FontFamilyFont_font 0 +int styleable FontFamilyFont_fontStyle 1 +int styleable FontFamilyFont_fontWeight 2 diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/libs/classes.jar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/libs/classes.jar new file mode 100644 index 00000000..4d82c9a6 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/libs/classes.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/proguard.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/proguard.txt new file mode 100644 index 00000000..63c0883b --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/proguard.txt @@ -0,0 +1 @@ +-dontwarn com.google.firebase.components.Component$Instantiation \ No newline at end of file diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/project.properties b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/project.properties new file mode 100644 index 00000000..d28e5065 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/project.properties @@ -0,0 +1,3 @@ +# Project target. +target=android-9 +android.library=true diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json new file mode 100644 index 00000000..0f8c2436 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.json @@ -0,0 +1 @@ +{"android arch-common":{"length":11361,"start":20},"android lifecycle runtime":{"length":11361,"start":11407},"android lifecycle-common":{"length":11361,"start":22793},"android support library annotations":{"length":11361,"start":34190},"android support library compat":{"length":11361,"start":45582},"android support library core ui":{"length":11361,"start":56975},"android support library core utils":{"length":11361,"start":68371},"android support library fragment":{"length":11361,"start":79765},"android support library media compat":{"length":11361,"start":91163},"android support library v4":{"length":11361,"start":102551},"play-services-basement":{"length":3,"start":113935},"play-services-tasks":{"length":3,"start":113958},"CCTZ":{"length":11360,"start":113966},"ICU4C":{"length":19443,"start":125332},"JSR 305":{"length":1588,"start":144783},"PCRE":{"length":3184,"start":146376},"Protobuf Nano":{"length":1734,"start":149574},"RE2":{"length":1560,"start":151312},"STL":{"length":682,"start":152876},"UTF":{"length":733,"start":153562},"darts_clone":{"length":1481,"start":154307},"flatbuffers":{"length":11360,"start":155800},"safeparcel":{"length":11360,"start":167171},"zlib":{"length":2502,"start":178536}} diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt new file mode 100644 index 00000000..7302c6e2 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/com.google.firebase.firebase-common-16.0.0/third_party_licenses.txt @@ -0,0 +1,3342 @@ +Android Arch-Common: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Lifecycle Runtime: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Lifecycle-Common: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Support Library Annotations: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Support Library compat: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Support Library core UI: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Support Library core utils: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Support Library fragment: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Support Library media compat: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Android Support Library v4: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +Play-services-basement: + + +Play-services-tasks: + + +CCTZ: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +ICU4C: +COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later) + +Copyright © 1991-2017 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in http://www.unicode.org/copyright.html + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder. + +--------------------- + +Third-Party Software Licenses + +This section contains third-party software notices and/or additional +terms for licensed third-party software components included within ICU +libraries. + +1. ICU License - ICU 1.8.1 to ICU 57.1 + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1995-2016 International Business Machines Corporation and others +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, and/or sell copies of the Software, and to permit persons +to whom the Software is furnished to do so, provided that the above +copyright notice(s) and this permission notice appear in all copies of +the Software and that both the above copyright notice(s) and this +permission notice appear in supporting documentation. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY +SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER +RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF +CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, use +or other dealings in this Software without prior written authorization +of the copyright holder. + +All trademarks and registered trademarks mentioned herein are the +property of their respective owners. + +2. Chinese/Japanese Word Break Dictionary Data (cjdict.txt) + + # The Google Chrome software developed by Google is licensed under + # the BSD license. Other software included in this distribution is + # provided under other licenses, as set forth below. + # + # The BSD License + # http://opensource.org/licenses/bsd-license.php + # Copyright (C) 2006-2008, Google Inc. + # + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions are met: + # + # Redistributions of source code must retain the above copyright notice, + # this list of conditions and the following disclaimer. + # Redistributions in binary form must reproduce the above + # copyright notice, this list of conditions and the following + # disclaimer in the documentation and/or other materials provided with + # the distribution. + # Neither the name of Google Inc. nor the names of its + # contributors may be used to endorse or promote products derived from + # this software without specific prior written permission. + # + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + # + # + # The word list in cjdict.txt are generated by combining three word lists + # listed below with further processing for compound word breaking. The + # frequency is generated with an iterative training against Google web + # corpora. + # + # * Libtabe (Chinese) + # - https://sourceforge.net/project/?group_id=1519 + # - Its license terms and conditions are shown below. + # + # * IPADIC (Japanese) + # - http://chasen.aist-nara.ac.jp/chasen/distribution.html + # - Its license terms and conditions are shown below. + # + # ---------COPYING.libtabe ---- BEGIN-------------------- + # + # /* + # * Copyright (c) 1999 TaBE Project. + # * Copyright (c) 1999 Pai-Hsiang Hsiao. + # * All rights reserved. + # * + # * Redistribution and use in source and binary forms, with or without + # * modification, are permitted provided that the following conditions + # * are met: + # * + # * . Redistributions of source code must retain the above copyright + # * notice, this list of conditions and the following disclaimer. + # * . Redistributions in binary form must reproduce the above copyright + # * notice, this list of conditions and the following disclaimer in + # * the documentation and/or other materials provided with the + # * distribution. + # * . Neither the name of the TaBE Project nor the names of its + # * contributors may be used to endorse or promote products derived + # * from this software without specific prior written permission. + # * + # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # * OF THE POSSIBILITY OF SUCH DAMAGE. + # */ + # + # /* + # * Copyright (c) 1999 Computer Systems and Communication Lab, + # * Institute of Information Science, Academia + # * Sinica. All rights reserved. + # * + # * Redistribution and use in source and binary forms, with or without + # * modification, are permitted provided that the following conditions + # * are met: + # * + # * . Redistributions of source code must retain the above copyright + # * notice, this list of conditions and the following disclaimer. + # * . Redistributions in binary form must reproduce the above copyright + # * notice, this list of conditions and the following disclaimer in + # * the documentation and/or other materials provided with the + # * distribution. + # * . Neither the name of the Computer Systems and Communication Lab + # * nor the names of its contributors may be used to endorse or + # * promote products derived from this software without specific + # * prior written permission. + # * + # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # * OF THE POSSIBILITY OF SUCH DAMAGE. + # */ + # + # Copyright 1996 Chih-Hao Tsai @ Beckman Institute, + # University of Illinois + # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4 + # + # ---------------COPYING.libtabe-----END-------------------------------- + # + # + # ---------------COPYING.ipadic-----BEGIN------------------------------- + # + # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science + # and Technology. All Rights Reserved. + # + # Use, reproduction, and distribution of this software is permitted. + # Any copy of this software, whether in its original form or modified, + # must include both the above copyright notice and the following + # paragraphs. + # + # Nara Institute of Science and Technology (NAIST), + # the copyright holders, disclaims all warranties with regard to this + # software, including all implied warranties of merchantability and + # fitness, in no event shall NAIST be liable for + # any special, indirect or consequential damages or any damages + # whatsoever resulting from loss of use, data or profits, whether in an + # action of contract, negligence or other tortuous action, arising out + # of or in connection with the use or performance of this software. + # + # A large portion of the dictionary entries + # originate from ICOT Free Software. The following conditions for ICOT + # Free Software applies to the current dictionary as well. + # + # Each User may also freely distribute the Program, whether in its + # original form or modified, to any third party or parties, PROVIDED + # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear + # on, or be attached to, the Program, which is distributed substantially + # in the same form as set out herein and that such intended + # distribution, if actually made, will neither violate or otherwise + # contravene any of the laws and regulations of the countries having + # jurisdiction over the User or the intended distribution itself. + # + # NO WARRANTY + # + # The program was produced on an experimental basis in the course of the + # research and development conducted during the project and is provided + # to users as so produced on an experimental basis. Accordingly, the + # program is provided without any warranty whatsoever, whether express, + # implied, statutory or otherwise. The term "warranty" used herein + # includes, but is not limited to, any warranty of the quality, + # performance, merchantability and fitness for a particular purpose of + # the program and the nonexistence of any infringement or violation of + # any right of any third party. + # + # Each user of the program will agree and understand, and be deemed to + # have agreed and understood, that there is no warranty whatsoever for + # the program and, accordingly, the entire risk arising from or + # otherwise connected with the program is assumed by the user. + # + # Therefore, neither ICOT, the copyright holder, or any other + # organization that participated in or was otherwise related to the + # development of the program and their respective officials, directors, + # officers and other employees shall be held liable for any and all + # damages, including, without limitation, general, special, incidental + # and consequential damages, arising out of or otherwise in connection + # with the use or inability to use the program or any product, material + # or result produced or otherwise obtained by using the program, + # regardless of whether they have been advised of, or otherwise had + # knowledge of, the possibility of such damages at any time during the + # project or thereafter. Each user will be deemed to have agreed to the + # foregoing by his or her commencement of use of the program. The term + # "use" as used herein includes, but is not limited to, the use, + # modification, copying and distribution of the program and the + # production of secondary products from the program. + # + # In the case where the program, whether in its original form or + # modified, was distributed or delivered to or received by a user from + # any person, organization or entity other than ICOT, unless it makes or + # grants independently of ICOT any specific warranty to the user in + # writing, such person, organization or entity, will also be exempted + # from and not be held liable to the user for any such damages as noted + # above as far as the program is concerned. + # + # ---------------COPYING.ipadic-----END---------------------------------- + +3. Lao Word Break Dictionary Data (laodict.txt) + + # Copyright (c) 2013 International Business Machines Corporation + # and others. All Rights Reserved. + # + # Project: http://code.google.com/p/lao-dictionary/ + # Dictionary: http://lao-dictionary.googlecode.com/git/Lao-Dictionary.txt + # License: http://lao-dictionary.googlecode.com/git/Lao-Dictionary-LICENSE.txt + # (copied below) + # + # This file is derived from the above dictionary, with slight + # modifications. + # ---------------------------------------------------------------------- + # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell. + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, + # are permitted provided that the following conditions are met: + # + # + # Redistributions of source code must retain the above copyright notice, this + # list of conditions and the following disclaimer. Redistributions in + # binary form must reproduce the above copyright notice, this list of + # conditions and the following disclaimer in the documentation and/or + # other materials provided with the distribution. + # + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + # OF THE POSSIBILITY OF SUCH DAMAGE. + # -------------------------------------------------------------------------- + +4. Burmese Word Break Dictionary Data (burmesedict.txt) + + # Copyright (c) 2014 International Business Machines Corporation + # and others. All Rights Reserved. + # + # This list is part of a project hosted at: + # github.com/kanyawtech/myanmar-karen-word-lists + # + # -------------------------------------------------------------------------- + # Copyright (c) 2013, LeRoy Benjamin Sharon + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions + # are met: Redistributions of source code must retain the above + # copyright notice, this list of conditions and the following + # disclaimer. Redistributions in binary form must reproduce the + # above copyright notice, this list of conditions and the following + # disclaimer in the documentation and/or other materials provided + # with the distribution. + # + # Neither the name Myanmar Karen Word Lists, nor the names of its + # contributors may be used to endorse or promote products derived + # from this software without specific prior written permission. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS + # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + # SUCH DAMAGE. + # -------------------------------------------------------------------------- + +5. Time Zone Database + + ICU uses the public domain data and code derived from Time Zone +Database for its time zone support. The ownership of the TZ database +is explained in BCP 175: Procedure for Maintaining the Time Zone +Database section 7. + + # 7. Database Ownership + # + # The TZ database itself is not an IETF Contribution or an IETF + # document. Rather it is a pre-existing and regularly updated work + # that is in the public domain, and is intended to remain in the + # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do + # not apply to the TZ Database or contributions that individuals make + # to it. Should any claims be made and substantiated against the TZ + # Database, the organization that is providing the IANA + # Considerations defined in this RFC, under the memorandum of + # understanding with the IETF, currently ICANN, may act in accordance + # with all competent court orders. No ownership claims will be made + # by ICANN or the IETF Trust on the database or the code. Any person + # making a contribution to the database or code waives all rights to + # future claims in that contribution or in the TZ Database. + +JSR 305: +Copyright (c) 2007-2009, JSR305 expert group +All rights reserved. + +http://www.opensource.org/licenses/bsd-license.php + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the JSR305 expert group nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +PCRE: +PCRE LICENCE +------------ + +PCRE is a library of functions to support regular expressions whose syntax +and semantics are as close as possible to those of the Perl 5 language. + +Release 8 of PCRE is distributed under the terms of the "BSD" licence, as +specified below. The documentation for PCRE, supplied in the "doc" +directory, is distributed under the same terms as the software itself. The data +in the testdata directory is not copyrighted and is in the public domain. + +The basic library functions are written in C and are freestanding. Also +included in the distribution is a set of C++ wrapper functions, and a +just-in-time compiler that can be used to optimize pattern matching. These +are both optional features that can be omitted when the library is built. + + +THE BASIC LIBRARY FUNCTIONS +--------------------------- + +Written by: Philip Hazel +Email local part: ph10 +Email domain: cam.ac.uk + +University of Cambridge Computing Service, +Cambridge, England. + +Copyright (c) 1997-2017 University of Cambridge +All rights reserved. + + +PCRE JUST-IN-TIME COMPILATION SUPPORT +------------------------------------- + +Written by: Zoltan Herczeg +Email local part: hzmester +Emain domain: freemail.hu + +Copyright(c) 2010-2017 Zoltan Herczeg +All rights reserved. + + +STACK-LESS JUST-IN-TIME COMPILER +-------------------------------- + +Written by: Zoltan Herczeg +Email local part: hzmester +Emain domain: freemail.hu + +Copyright(c) 2009-2017 Zoltan Herczeg +All rights reserved. + + +THE C++ WRAPPER FUNCTIONS +------------------------- + +Contributed by: Google Inc. + +Copyright (c) 2007-2012, Google Inc. +All rights reserved. + + +THE "BSD" LICENCE +----------------- + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the name of Google + Inc. nor the names of their contributors may be used to endorse or + promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +End + +Protobuf Nano: +Copyright 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Code generated by the Protocol Buffer compiler is owned by the owner +of the input file used when generating it. This code is not +standalone and requires a support library to be linked with it. This +support library is itself covered by the above license. + +RE2: +// Copyright (c) 2009 The RE2 Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +STL: +SGI STL + +The STL portion of GNU libstdc++ that is used with gcc3 and gcc4 is licensed +under the GPL, with the following exception: + +# As a special exception, you may use this file as part of a free software +# library without restriction. Specifically, if other files instantiate +# templates or use macros or inline functions from this file, or you compile +# this file and link it with other files to produce an executable, this +# file does not by itself cause the resulting executable to be covered by +# the GNU General Public License. This exception does not however +# invalidate any other reasons why the executable file might be covered by +# the GNU General Public License. + + +UTF: +UTF-8 Library + +The authors of this software are Rob Pike and Ken Thompson. + Copyright (c) 1998-2002 by Lucent Technologies. +Permission to use, copy, modify, and distribute this software for any +purpose without fee is hereby granted, provided that this entire notice +is included in all copies of any software which is or includes a copy +or modification of this software and in all copies of the supporting +documentation for such software. +THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY +REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY +OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + +darts_clone: +Copyright (c) 2008-2011, Susumu Yata +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +- Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +flatbuffers: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +safeparcel: + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +zlib: +(extracted from README, except for match.S) + +Copyright notice: + + (C) 1995-2013 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +If you use the zlib library in a product, we would appreciate *not* receiving +lengthy legal documents to sign. The sources are provided for free but without +warranty of any kind. The library has been entirely written by Jean-loup +Gailly and Mark Adler; it does not include third-party code. + +If you redistribute modified sources, we would appreciate that you include in +the file ChangeLog history information documenting your changes. Please read +the FAQ for more information on the distribution of modified source versions. + +(extracted from match.S, for match.S only) + +Copyright (C) 1998, 2007 Brian Raiter + +This software is provided 'as-is', without any express or implied +warranty. In no event will the author be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/org.test.psr.classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/org.test.psr.classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsJetifier/org.test.psr.classifier-1.0.1-foo.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ProjectSettings/GvhProjectSettings.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ProjectSettings/GvhProjectSettings.xml new file mode 100644 index 00000000..20f2d7b5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ProjectSettings/GvhProjectSettings.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1-bar.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1-bar.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1-bar.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1-foo.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1-foo.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1-foo.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1.aar b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1.aar new file mode 100644 index 00000000..a9defc42 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1.pom b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1.pom new file mode 100644 index 00000000..9e3d37d0 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/1.0.1/classifier-1.0.1.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr + classifier + 1.0.1 + aar + + + + + diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/maven-metadata.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/maven-metadata.xml new file mode 100644 index 00000000..ba1496c5 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/project_relative_path/repo/org/test/psr/classifier/maven-metadata.xml @@ -0,0 +1,13 @@ + + org.test.psr + classifier + + 1.0.1 + + 1.0.1 + + + + + + diff --git a/source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestResolveAsync.cs b/source/AndroidResolver/test/src/AndroidResolverIntegrationTests.cs similarity index 56% rename from source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestResolveAsync.cs rename to source/AndroidResolver/test/src/AndroidResolverIntegrationTests.cs index bb2fdb35..cc7a6e66 100644 --- a/source/PlayServicesResolver/test/resolve_async/Assets/PlayServicesResolver/Editor/TestResolveAsync.cs +++ b/source/AndroidResolver/test/src/AndroidResolverIntegrationTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (C) 2018 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,195 +21,293 @@ using System.Linq; using System.Reflection; -[UnityEditor.InitializeOnLoad] -public class TestResolveAsync { +using Google.JarResolver; +using GooglePlayServices; - /// - /// Test case class. - /// - /// This specifies a test case to execute. Each test case has a name which is used to - /// log the result of the test and the method to execute as part of the test case. - /// - class TestCase { - - /// - /// Test case delegate. - /// - /// Object executing this method. - /// Called when the test case is complete. - public delegate void MethodDelegate(TestCase testCase, - Action testCaseComplete); +namespace Google { - /// - /// Name of the test case. - /// - public string Name { get; set; } - - /// - /// Delegate that runs the test case logic. - /// - public MethodDelegate Method { get; set; } - } +public class AndroidResolverIntegrationTests { /// - /// Result of a test. + /// EditorUserBuildSettings property which controls the Android build system. /// - class TestCaseResult { - - /// - /// Initialize the class. - /// - public TestCaseResult(TestCase testCase) { - TestCaseName = testCase.Name; - ErrorMessages = new List(); - Skipped = false; - } - - /// - /// Name of the test case. This does not need to be set by the test case. - /// - public string TestCaseName { private get; set; } - - /// - /// Error messages reported by a test case failure. - /// - public List ErrorMessages { get; set; } - - /// - /// Whether the test case was skipped. - /// - public bool Skipped { get; set; } - - /// - /// Whether the test case succeeded. - /// - public bool Succeeded { - get { - return Skipped || ErrorMessages == null || ErrorMessages.Count == 0; - } - } + private const string ANDROID_BUILD_SYSTEM = "androidBuildSystem"; - /// - /// Format the result as a string. - /// - /// Include failure messages in the list. - public string FormatString(bool includeFailureMessages) { - return String.Format("Test {0}: {1}{2}", TestCaseName, - Skipped ? "SKIPPED" : Succeeded ? "PASSED" : "FAILED", - includeFailureMessages && ErrorMessages != null && - ErrorMessages.Count > 0 ? - "\n" + String.Join("\n", ErrorMessages.ToArray()) : ""); - } - } + /// + /// EditorUserBuildSettings property which controls whether an Android project is exported. + /// + private const string EXPORT_ANDROID_PROJECT = "exportAsGoogleAndroidProject"; /// - /// Executed test case names and failure messages (if any). + /// The name of the file, without extension, that will serve as a template for dynamically + /// adding additional dependencies. /// - private static List testCaseResults = new List(); + private const string ADDITIONAL_DEPENDENCIES_FILENAME = "TestAdditionalDependencies"; /// - /// Set of test cases to execute. + /// The name of the file, without extension, that will serve as a template for dynamically + /// adding additional dependencies with a duplicate package with a different version. /// - private static List testCases = new List(); + private const string ADDITIONAL_DUPLICATE_DEPENDENCIES_FILENAME = "TestAdditionalDuplicateDependencies"; /// - /// EditorUserBuildSettings property which controls the Android build system. + /// Disabled application Gradle template file. /// - private const string ANDROID_BUILD_SYSTEM = "androidBuildSystem"; + private const string GRADLE_TEMPLATE_DISABLED = + "Assets/Plugins/Android/mainTemplateDISABLED.gradle"; /// - /// EditorUserBuildSettings property which controls whether an Android project is exported. + /// Disabled library Gradle template file. /// - private const string EXPORT_ANDROID_PROJECT = "exportAsGoogleAndroidProject"; + private const string GRADLE_TEMPLATE_LIBRARY_DISABLED = + "Assets/Plugins/Android/mainTemplateLibraryDISABLED.gradle"; /// - /// The name of the file, without extension, that will serve as a template for dynamically - /// adding additional dependencies. + /// Disabled Gradle properties template file. /// - private const string ADDITIONAL_DEPENDENCIES_FILENAME = "TestAdditionalDependencies"; + private const string GRADLE_TEMPLATE_PROPERTIES_DISABLED = + "Assets/Plugins/Android/gradleTemplateDISABLED.properties"; /// - /// Disabled Gradle template file. + /// Disabled Gradle settings template file. /// - private const string GRADLE_TEMPLATE_DISABLED = - "Assets/Plugins/Android/mainTemplateDISABLED.gradle"; + private const string GRADLE_TEMPLATE_SETTINGS_DISABLED = + "Assets/Plugins/Android/settingsTemplateDISABLED.gradle"; + /// /// /// Enabled Gradle template file. /// private const string GRADLE_TEMPLATE_ENABLED = "Assets/Plugins/Android/mainTemplate.gradle"; /// - /// Major / minor Unity version numbers. + /// + /// Enabled Gradle template properties file. /// - private static float unityVersion; + private const string GRADLE_TEMPLATE_PROPERTIES_ENABLED = "Assets/Plugins/Android/gradleTemplate.properties"; /// - /// This module can be executed multiple times when the Version Handler is enabling - /// so this method uses a temporary file to determine whether the module has been executed - /// once in a Unity session. + /// + /// Enabled Gradle settings properties file. /// - /// true if the module was previously initialized, false otherwise. - private static bool SetInitialized() { - const string INITIALIZED_PATH = "Temp/TestEnabledCallbackInitialized"; - if (File.Exists(INITIALIZED_PATH)) return true; - File.WriteAllText(INITIALIZED_PATH, "Ready"); - return false; - } + private const string GRADLE_TEMPLATE_SETTINGS_ENABLED = "Assets/Plugins/Android/settingsTemplate.gradle"; /// - /// Register a method to call when the Version Handler has enabled all plugins in the project. + /// Configure tests to run. /// - static TestResolveAsync() { - unityVersion = Google.VersionHandler.GetUnityVersionMajorMinor(); - // Disable stack traces for more condensed logs. - UnityEngine.Application.stackTraceLogType = UnityEngine.StackTraceLogType.None; + [IntegrationTester.Initializer] + public static void ConfigureTestCases() { + // The default application name is different in different versions of Unity. Set the value + // in the beginning of the test to ensure all AndroidManifest.xml are using the same + // application name across different versions of Unity. + UnityCompat.SetApplicationId(UnityEditor.BuildTarget.Android, "com.Company.ProductName"); // Set of files to ignore (relative to the Assets/Plugins/Android directory) in all tests // that do not use the Gradle template. var nonGradleTemplateFilesToIgnore = new HashSet() { - Path.GetFileName(GRADLE_TEMPLATE_DISABLED) + Path.GetFileName(GRADLE_TEMPLATE_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_PROPERTIES_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_SETTINGS_DISABLED) + }; + + // Set of files to ignore (relative to the Assets/Plugins/Android directory) in all tests + // that do not use the Gradle template. + var unity2022WithoutJetifierGradleTemplateFilesToIgnore = new HashSet { + Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_PROPERTIES_DISABLED), }; + var defaultGradleTemplateFilesToIgnore = new HashSet { + Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_PROPERTIES_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_SETTINGS_DISABLED), + }; + UnityEngine.Debug.Log("Setting up test cases for execution."); - testCases.AddRange(new [] { + IntegrationTester.Runner.ScheduleTestCases(new [] { // This *must* be the first test case as other test cases depend upon it. - new TestCase { + new IntegrationTester.TestCase { Name = "ValidateAndroidTargetSelected", Method = ValidateAndroidTargetSelected, }, - new TestCase { + new IntegrationTester.TestCase { Name = "SetupDependencies", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); - var testCaseResult = new TestCaseResult(testCase); + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); ValidateDependencies(testCaseResult); testCaseComplete(testCaseResult); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemWithTemplate", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); + string expectedAssetsDir = null; + string gradleTemplateSettings = null; + HashSet filesToIgnore = null; + if (UnityChangeMavenInSettings_2022_2) { + // For Unity >= 2022.2, Maven repo need to be injected to + // Gradle Settings Template, instead of Gradle Main Template. + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplate_2022_2"; + gradleTemplateSettings = GRADLE_TEMPLATE_SETTINGS_DISABLED; + filesToIgnore = unity2022WithoutJetifierGradleTemplateFilesToIgnore; + } else { + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplate"; + filesToIgnore = defaultGradleTemplateFilesToIgnore; + } + + ResolveWithGradleTemplate( + GRADLE_TEMPLATE_DISABLED, + expectedAssetsDir, + testCase, testCaseComplete, + otherExpectedFiles: new [] { + "Assets/GeneratedLocalRepo/Firebase/m2repository/com/google/" + + "firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.aar" }, + filesToIgnore: filesToIgnore, + deleteGradleTemplateSettings: true, + gradleTemplateSettings: gradleTemplateSettings); + } + }, + new IntegrationTester.TestCase { + Name = "ResolveForGradleBuildSystemWithDuplicatePackages", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + SetupDependencies(); + // Add 2 additional dependency files (each file contains a single package + // but with different versions). + UpdateAdditionalDependenciesFile(true, ADDITIONAL_DEPENDENCIES_FILENAME); + UpdateAdditionalDependenciesFile(true, ADDITIONAL_DUPLICATE_DEPENDENCIES_FILENAME); + + string expectedAssetsDir = null; + string gradleTemplateSettings = null; + HashSet filesToIgnore = null; + if (UnityChangeMavenInSettings_2022_2) { + // For Unity >= 2022.2, Maven repo need to be injected to + // Gradle Settings Template, instead of Gradle Main Template. + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages_2022_2"; + gradleTemplateSettings = GRADLE_TEMPLATE_SETTINGS_DISABLED; + filesToIgnore = unity2022WithoutJetifierGradleTemplateFilesToIgnore; + } else { + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages"; + filesToIgnore = defaultGradleTemplateFilesToIgnore; + } + + ResolveWithGradleTemplate( + GRADLE_TEMPLATE_DISABLED, + expectedAssetsDir, + testCase, testCaseComplete, + otherExpectedFiles: new [] { + "Assets/GeneratedLocalRepo/Firebase/m2repository/com/google/" + + "firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.aar" }, + filesToIgnore: filesToIgnore, + deleteGradleTemplateSettings: true, + gradleTemplateSettings: gradleTemplateSettings); + } + }, + new IntegrationTester.TestCase { + Name = "ResolverForGradleBuildSystemWithTemplateUsingJetifier", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + SetupDependencies(); + GooglePlayServices.SettingsDialog.UseJetifier = true; + + string expectedAssetsDir = null; + string gradleTemplateProperties = null; + string gradleTemplateSettings = null; + HashSet filesToIgnore = null; + if (UnityChangeMavenInSettings_2022_2) { + // For Unity >= 2022.2, Maven repo need to be injected to + // Gradle Settings Template, instead of Gradle Main Template. + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplateJetifier_2022_2"; + gradleTemplateProperties = GRADLE_TEMPLATE_PROPERTIES_DISABLED; + gradleTemplateSettings = GRADLE_TEMPLATE_SETTINGS_DISABLED; + filesToIgnore = new HashSet { + Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED), + }; + } else if (UnityChangeJetifierInProperties_2019_3) { + // For Unity >= 2019.3f, Jetifier is enabled for the build + // via gradle properties. + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplateJetifier_2019_3"; + gradleTemplateProperties = GRADLE_TEMPLATE_PROPERTIES_DISABLED; + filesToIgnore = new HashSet { + Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_SETTINGS_DISABLED), + }; + } else { + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplateJetifier"; + filesToIgnore = new HashSet { + Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_PROPERTIES_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_SETTINGS_DISABLED), + }; + } + + ResolveWithGradleTemplate( + GRADLE_TEMPLATE_DISABLED, + expectedAssetsDir, + testCase, testCaseComplete, + otherExpectedFiles: new [] { + "Assets/GeneratedLocalRepo/Firebase/m2repository/com/google/" + + "firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.aar" }, + filesToIgnore: filesToIgnore, + deleteGradleTemplateProperties: true, + gradleTemplateProperties: gradleTemplateProperties, + deleteGradleTemplateSettings: true, + gradleTemplateSettings: gradleTemplateSettings); + } + }, + new IntegrationTester.TestCase { + Name = "ResolveForGradleBuildSystemLibraryWithTemplate", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + SetupDependencies(); + + string expectedAssetsDir = null; + string gradleTemplateSettings = null; + HashSet filesToIgnore = null; + if (UnityChangeMavenInSettings_2022_2) { + // For Unity >= 2022.2, Maven repo need to be injected to + // Gradle Settings Template, instead of Gradle Main Template. + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplateLibrary_2022_2"; + gradleTemplateSettings = GRADLE_TEMPLATE_SETTINGS_DISABLED; + filesToIgnore = new HashSet { + Path.GetFileName(GRADLE_TEMPLATE_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_PROPERTIES_DISABLED), + }; + } else { + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplateLibrary"; + filesToIgnore = new HashSet { + Path.GetFileName(GRADLE_TEMPLATE_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_PROPERTIES_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_SETTINGS_DISABLED), + }; + } + ResolveWithGradleTemplate( - "ExpectedArtifacts/NoExport/GradleTemplate", + GRADLE_TEMPLATE_LIBRARY_DISABLED, + expectedAssetsDir, testCase, testCaseComplete, otherExpectedFiles: new [] { - "Assets/Firebase/m2repository/com/google/firebase/" + - "firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.aar" }); + "Assets/GeneratedLocalRepo/Firebase/m2repository/com/google/" + + "firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.aar" }, + filesToIgnore: filesToIgnore, + deleteGradleTemplateSettings: true, + gradleTemplateSettings: gradleTemplateSettings); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemWithTemplateEmpty", Method = (testCase, testCaseComplete) => { string enabledDependencies = - "Assets/PlayServicesResolver/Editor/TestDependencies.xml"; + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml"; string disabledDependencies = - "Assets/PlayServicesResolver/Editor/TestDependenciesDISABLED.xml"; + "Assets/ExternalDependencyManager/Editor/TestDependenciesDISABLED.xml"; Action enableDependencies = () => { UnityEditor.AssetDatabase.MoveAsset(disabledDependencies, enabledDependencies); @@ -219,23 +317,29 @@ static TestResolveAsync() { var error = UnityEditor.AssetDatabase.MoveAsset(enabledDependencies, disabledDependencies); if (!String.IsNullOrEmpty(error)) { - testCaseComplete(new TestCaseResult(testCase) { + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { ErrorMessages = new List() { error } }); return; } ClearAllDependencies(); ResolveWithGradleTemplate( + GRADLE_TEMPLATE_DISABLED, "ExpectedArtifacts/NoExport/GradleTemplateEmpty", testCase, (testCaseResult) => { enableDependencies(); testCaseComplete(testCaseResult); + }, + filesToIgnore: new HashSet { + Path.GetFileName(GRADLE_TEMPLATE_LIBRARY_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_PROPERTIES_DISABLED), + Path.GetFileName(GRADLE_TEMPLATE_SETTINGS_DISABLED) }); } finally { enableDependencies(); } } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystem", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); @@ -244,7 +348,7 @@ static TestResolveAsync() { null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemSync", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); @@ -254,19 +358,7 @@ static TestResolveAsync() { synchronous: true); } }, - new TestCase { - Name = "ResolveForInternalBuildSystem", - Method = (testCase, testCaseComplete) => { - ClearAllDependencies(); - SetupDependencies(); - Resolve("Internal", false, - AarsWithNativeLibrariesSupported ? - "ExpectedArtifacts/NoExport/InternalNativeAars" : - "ExpectedArtifacts/NoExport/InternalNativeAarsExploded", - null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); - } - }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemAndExport", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); @@ -275,7 +367,7 @@ static TestResolveAsync() { null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveAddedDependencies", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); @@ -285,7 +377,7 @@ static TestResolveAsync() { null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveRemovedDependencies", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); @@ -297,7 +389,7 @@ static TestResolveAsync() { null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "DeleteResolvedLibraries", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); @@ -305,8 +397,7 @@ static TestResolveAsync() { Resolve("Gradle", true, "ExpectedArtifacts/Export/Gradle", null, nonGradleTemplateFilesToIgnore, testCase, (testCaseResult) => { - Google.VersionHandler.InvokeStaticMethod( - AndroidResolverClass, "DeleteResolvedLibrariesSync", null); + PlayServicesResolver.DeleteResolvedLibrariesSync(); var unexpectedFilesMessage = new List(); var resolvedFiles = ListFiles("Assets/Plugins/Android", nonGradleTemplateFilesToIgnore); @@ -322,98 +413,145 @@ static TestResolveAsync() { synchronous: true); } }, - new TestCase { + new IntegrationTester.TestCase { Name = "ResolveForGradleBuildSystemWithTemplateDeleteLibraries", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); SetupDependencies(); + string expectedAssetsDir = null; + string gradleTemplateSettings = null; + HashSet filesToIgnore = null; + if (UnityChangeMavenInSettings_2022_2) { + // For Unity >= 2022.2, Maven repo need to be injected to + // Gradle Settings Template, instead of Gradle Main Template. + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplate_2022_2"; + gradleTemplateSettings = GRADLE_TEMPLATE_SETTINGS_DISABLED; + filesToIgnore = unity2022WithoutJetifierGradleTemplateFilesToIgnore; + } else { + expectedAssetsDir = "ExpectedArtifacts/NoExport/GradleTemplate"; + filesToIgnore = defaultGradleTemplateFilesToIgnore; + } + ResolveWithGradleTemplate( - "ExpectedArtifacts/NoExport/GradleTemplate", + GRADLE_TEMPLATE_DISABLED, + expectedAssetsDir, testCase, (testCaseResult) => { - Google.VersionHandler.InvokeStaticMethod( - AndroidResolverClass, "DeleteResolvedLibrariesSync", null); + PlayServicesResolver.DeleteResolvedLibrariesSync(); + string expectedAssetsDirEmpty = null; + if (UnityChangeMavenInSettings_2022_2) { + // For Unity >= 2022.2, Maven repo need to be injected to + // Gradle Settings Template, instead of Gradle Main Template. + expectedAssetsDirEmpty = "ExpectedArtifacts/NoExport/GradleTemplateEmpty_2022_2"; + } else { + expectedAssetsDirEmpty = "ExpectedArtifacts/NoExport/GradleTemplateEmpty"; + } testCaseResult.ErrorMessages.AddRange(CompareDirectoryContents( - "ExpectedArtifacts/NoExport/GradleTemplateEmpty", - "Assets/Plugins/Android", null)); + expectedAssetsDirEmpty, + "Assets/Plugins/Android", filesToIgnore)); if (File.Exists(GRADLE_TEMPLATE_ENABLED)) { File.Delete(GRADLE_TEMPLATE_ENABLED); } + if (File.Exists(GRADLE_TEMPLATE_SETTINGS_ENABLED)) { + File.Delete(GRADLE_TEMPLATE_SETTINGS_ENABLED); + } testCaseComplete(testCaseResult); - }, deleteGradleTemplate: false); + }, + deleteGradleTemplate: false, + filesToIgnore: filesToIgnore, + deleteGradleTemplateSettings: false, + gradleTemplateSettings: gradleTemplateSettings); } }, }); - // Test resolution with Android ABI filtering. - if (unityVersion >= 2018.0f) { - testCases.AddRange(new [] { - new TestCase { - Name = "ResolverForGradleBuildSystemUsingAbisArmeabiv7aAndArm64", + // Internal build system for Android is removed in Unity 2019, even + // UnityEditor.AndroidBuildSystem.Internal still exist. + if (IntegrationTester.Runner.UnityVersion < 2019.0f) { + IntegrationTester.Runner.ScheduleTestCases(new [] { + new IntegrationTester.TestCase { + Name = "ResolveForInternalBuildSystem", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); - Resolve("Gradle", false, - "ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64", - "armeabi-v7a, arm64-v8a", nonGradleTemplateFilesToIgnore, - testCase, testCaseComplete); + SetupDependencies(); + Resolve("Internal", false, AarsWithNativeLibrariesSupported ? + "ExpectedArtifacts/NoExport/InternalNativeAars" : + "ExpectedArtifacts/NoExport/InternalNativeAarsExploded", + null, nonGradleTemplateFilesToIgnore, testCase, + testCaseComplete); } - } - }); - } else if (unityVersion >= 5.0f) { - testCases.AddRange(new [] { - new TestCase { - Name = "ResolverForGradleBuildSystemUsingAbisArmeabiv7a", + }, + new IntegrationTester.TestCase { + Name = "ResolveForInternalBuildSystemUsingJetifier", Method = (testCase, testCaseComplete) => { ClearAllDependencies(); - Resolve("Gradle", false, - "ExpectedArtifacts/NoExport/GradleArmeabiv7a", - "armeabi-v7a", nonGradleTemplateFilesToIgnore, - testCase, testCaseComplete); + SetupDependencies(); + GooglePlayServices.SettingsDialog.UseJetifier = true; + Resolve("Internal", false, AarsWithNativeLibrariesSupported ? + "ExpectedArtifacts/NoExport/InternalNativeAarsJetifier" : + "ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier", + null, nonGradleTemplateFilesToIgnore, testCase, + testCaseComplete); } - } + }, }); } - UnityEngine.Debug.Log("Set up callback on Version Handler completion."); - Google.VersionHandler.UpdateCompleteMethods = new [] { - ":TestResolveAsync:VersionHandlerReady" - }; - UnityEngine.Debug.Log("Enable plugin using the Version Handler."); - Google.VersionHandler.UpdateNow(); + // Test resolution with Android ABI filtering. + if (IntegrationTester.Runner.UnityVersion >= 2018.0f) { + IntegrationTester.Runner.ScheduleTestCase( + new IntegrationTester.TestCase { + Name = "ResolverForGradleBuildSystemUsingAbisArmeabiv7aAndArm64", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + Resolve("Gradle", false, + "ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64", + "armeabi-v7a, arm64-v8a", nonGradleTemplateFilesToIgnore, + testCase, testCaseComplete); + } + }); + } else if (IntegrationTester.Runner.UnityVersion >= 5.0f) { + IntegrationTester.Runner.ScheduleTestCase( + new IntegrationTester.TestCase { + Name = "ResolverForGradleBuildSystemUsingAbisArmeabiv7a", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + Resolve("Gradle", false, + "ExpectedArtifacts/NoExport/GradleArmeabiv7a", + "armeabi-v7a", nonGradleTemplateFilesToIgnore, + testCase, testCaseComplete); + } + }); + } } /// - /// Whether the Gradle builds are supported by the current version of Unity. + /// Whether Current Unity Version requires Maven Repo in Gradle Settings Template. /// - private static bool GradleBuildSupported { - get { return unityVersion >= 5.5f; } + private static bool UnityChangeMavenInSettings_2022_2 { + get { return IntegrationTester.Runner.UnityVersion >= 2022.2f; } } /// - /// Whether the current version of Unity requires AARs with native artifacts to be converted - /// to ant / eclipse projects. + /// Whether Current Unity Version requires Jetifier enabling in Gradle Properties Template. /// - private static bool AarsWithNativeLibrariesSupported { - get { return unityVersion < 2017.0f; } + private static bool UnityChangeJetifierInProperties_2019_3 { + get { return IntegrationTester.Runner.UnityVersion >= 2019.3f; } } /// - /// Get property that gets and sets Android ABIs. + /// Whether the Gradle builds are supported by the current version of Unity. /// - private static PropertyInfo AndroidAbisCurrentStringProperty { - get { - return Google.VersionHandler.FindClass( - "Google.JarResolver", "GooglePlayServices.AndroidAbis").GetProperty( - "CurrentString"); - } + private static bool GradleBuildSupported { + get { return IntegrationTester.Runner.UnityVersion >= 5.5f; } } /// - /// Set Android ABIs. + /// Whether the current version of Unity requires AARs with native artifacts to be converted + /// to ant / eclipse projects. /// - private static string AndroidAbisCurrentString { - set { AndroidAbisCurrentStringProperty.SetValue(null, value, null); } - get { return (string)AndroidAbisCurrentStringProperty.GetValue(null, null); } + private static bool AarsWithNativeLibrariesSupported { + get { return IntegrationTester.Runner.UnityVersion < 2017.0f; } } /// @@ -457,139 +595,38 @@ private static object StringToAndroidBuildSystemValue(string value) { return Enum.Parse(androidBuildSystemType, value); } - - /// - /// Log test result summary and quit the application. - /// - private static void LogSummaryAndExit() { - bool passed = true; - var testSummaryLines = new List(); - foreach (var testCaseResult in testCaseResults) { - testSummaryLines.Add(testCaseResult.FormatString(false)); - passed &= testCaseResult.Succeeded; - } - UnityEngine.Debug.Log(String.Format("Test(s) {0}.\n{1}", passed ? "PASSED" : "FAILED", - String.Join("\n", testSummaryLines.ToArray()))); - UnityEditor.EditorApplication.Exit(passed ? 0 : 1); - } - - /// - /// Log a test case result with error details. - /// - /// Result to log. - private static void LogTestCaseResult(TestCaseResult testCaseResult) { - testCaseResults.Add(testCaseResult); - UnityEngine.Debug.Log(testCaseResult.FormatString(true)); - } - - /// - /// Execute a function for a test case catching any exceptions and logging the result. - /// - /// Object executing this method. - /// Action to execute. - /// Whether to execute the next test case if the specified action - /// fails. - /// true if the action executed without any exceptions, false otherwise. - private static bool ExecuteTestCase(TestCase testCase, Action testCaseAction, - bool executeNext) { - bool succeeded = true; - try { - testCaseAction(); - } catch (Exception e) { - LogTestCaseResult(new TestCaseResult(testCase) { - ErrorMessages = new List { e.ToString() } - }); - succeeded = false; - } - if (!succeeded && executeNext) ExecuteNextTestCase(); - return succeeded; - } - - /// - /// Execute the next queued test case. - /// - private static void ExecuteNextTestCase() { - bool executeNext; - do { - executeNext = false; - if (testCases.Count > 0) { - var testCase = testCases[0]; - testCases.RemoveAt(0); - UnityEngine.Debug.Log(String.Format("Test {0} starting...", testCase.Name)); - // If the test threw an exception on this thread, execute the next test case - // in a loop. - executeNext = !ExecuteTestCase( - testCase, - () => { - testCase.Method(testCase, (testCaseResult) => { - UnityEngine.Debug.Log(String.Format("Test {0} complete", - testCase.Name)); - testCaseResult.TestCaseName = testCase.Name; - LogTestCaseResult(testCaseResult); - ExecuteNextTestCase(); - }); - }, false); - } else { - LogSummaryAndExit(); - } - } while (executeNext); - } - - /// - /// Called when the Version Handler has enabled all managed plugins in a project. - /// - public static void VersionHandlerReady() { - UnityEngine.Debug.Log("VersionHandler is ready."); - Google.VersionHandler.UpdateCompleteMethods = null; - // If this has already been initialize this session, do not start tests again. - if (SetInitialized()) return; - // Start executing tests. - ExecuteNextTestCase(); - } - /// /// Make sure the Android platform is selected for testing. /// - private static void ValidateAndroidTargetSelected(TestCase testCase, - Action testCaseComplete) { + private static void ValidateAndroidTargetSelected( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { if (UnityEditor.EditorUserBuildSettings.activeBuildTarget != UnityEditor.BuildTarget.Android) { - LogTestCaseResult(new TestCaseResult(testCase) { + IntegrationTester.Runner.LogTestCaseResult( + new IntegrationTester.TestCaseResult(testCase) { ErrorMessages = new List() { "Target platform must be Android" } }); - LogSummaryAndExit(); + IntegrationTester.Runner.LogSummaryAndExit(); } - testCaseComplete(new TestCaseResult(testCase)); - } - /// - /// Get the Android Resolver support instance. - /// NOTE: This is deprecated and only exposed for testing. - /// - private static object AndroidResolverSupport { - get { - // Get the deprecated dependency management API. - return Google.VersionHandler.InvokeStaticMethod( - Google.VersionHandler.FindClass( - "Google.JarResolver", "Google.JarResolver.PlayServicesSupport"), - "CreateInstance", new object[] { "Test", null, "ProjectSettings" }); - } - } + // Verify if PlayServicesResolver properties are working properly. + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); - /// - /// Cached Android Resolver class. - /// - private static Type androidResolverClass = null; + if (String.IsNullOrEmpty(PlayServicesResolver.AndroidGradlePluginVersion)) { + testCaseResult.ErrorMessages.Add(String.Format( + "PlayServicesResolver.AndroidGradlePluginVersion is empty or null")); + } - /// - /// Get the Android Resolver class. - /// - private static Type AndroidResolverClass { - get { - androidResolverClass = androidResolverClass ?? Google.VersionHandler.FindClass( - "Google.JarResolver", "GooglePlayServices.PlayServicesResolver"); - return androidResolverClass; + if (String.IsNullOrEmpty(PlayServicesResolver.GradleVersion)) { + testCaseResult.ErrorMessages.Add(String.Format( + "PlayServicesResolver.GradleVersion is empty or null")); } + + // Also, set the internal Gradle version to a deterministic version number. This controls + // how gradle template snippets are generated by GradleTemplateResolver. + PlayServicesResolver.GradleVersion = "2.14"; + testCaseComplete(testCaseResult); } /// @@ -601,11 +638,15 @@ private static Type AndroidResolverClass { /// private static void ClearAllDependencies() { UnityEngine.Debug.Log("Clear all loaded dependencies"); - AndroidResolverSupport.GetType().GetMethod( - "ResetDependencies", - BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null); + GooglePlayServices.SettingsDialog.UseJetifier = false; + GooglePlayServices.SettingsDialog.PatchPropertiesTemplateGradle = false; + GooglePlayServices.SettingsDialog.PatchSettingsTemplateGradle = false; + + GooglePlayServices.SettingsDialog.UserRejectedGradleUpgrade = true; - UpdateAdditionalDependenciesFile(false); + PlayServicesSupport.ResetDependencies(); + UpdateAdditionalDependenciesFile(false, ADDITIONAL_DEPENDENCIES_FILENAME); + UpdateAdditionalDependenciesFile(false, ADDITIONAL_DUPLICATE_DEPENDENCIES_FILENAME); } /// @@ -614,9 +655,8 @@ private static void ClearAllDependencies() { /// future. /// private static void SetupDependencies() { - Google.VersionHandler.InvokeInstanceMethod( - AndroidResolverSupport, "DependOn", - new object[] { "com.google.firebase", "firebase-common", "16.0.0" }); + PlayServicesSupport.CreateInstance("Test", null, "ProjectSettings").DependOn( + "com.google.firebase", "firebase-common", "16.0.0"); } /// @@ -624,39 +664,40 @@ private static void SetupDependencies() { /// /// TestCaseResult instance to add errors to if this method /// fails. - private static void ValidateDependencies(TestCaseResult testCaseResult) { + private static void ValidateDependencies(IntegrationTester.TestCaseResult testCaseResult) { // Validate set dependencies are present. CompareKeyValuePairLists( new List>() { new KeyValuePair( "com.android.support:support-annotations:26.1.0", - "Assets/PlayServicesResolver/Editor/TestDependencies.xml:4"), + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:4"), new KeyValuePair( "com.google.firebase:firebase-app-unity:5.1.1", - "Assets/PlayServicesResolver/Editor/TestDependencies.xml:10"), + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10"), new KeyValuePair( "com.google.firebase:firebase-common:16.0.0", - "TestResolveAsync.SetupDependencies()") + "Google.AndroidResolverIntegrationTests.SetupDependencies"), + new KeyValuePair( + "org.test.psr:classifier:1.0.1:foo@aar", + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12"), }, - (IList>)Google.VersionHandler.InvokeStaticMethod( - AndroidResolverClass, "GetPackageSpecs", null), + PlayServicesResolver.GetPackageSpecs(), "Package Specs", testCaseResult); // Validate configured repos are present. CompareKeyValuePairLists( new List>() { new KeyValuePair( "file:///my/nonexistant/test/repo", - "Assets/PlayServicesResolver/Editor/TestDependencies.xml:15"), + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17"), new KeyValuePair( "file:///" + Path.GetFullPath("project_relative_path/repo").Replace("\\", "/"), - "Assets/PlayServicesResolver/Editor/TestDependencies.xml:15"), + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17"), new KeyValuePair( "file:///" + Path.GetFullPath( "Assets/Firebase/m2repository").Replace("\\", "/"), - "Assets/PlayServicesResolver/Editor/TestDependencies.xml:10") + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10") }, - (IList>)Google.VersionHandler.InvokeStaticMethod( - AndroidResolverClass, "GetRepos", null), + PlayServicesResolver.GetRepos(), "Repos", testCaseResult); } @@ -671,7 +712,7 @@ private static void ValidateDependencies(TestCaseResult testCaseResult) { private static void CompareKeyValuePairLists( IList> expectedList, IList> testList, string listDescription, - TestCaseResult testCaseResult) { + IntegrationTester.TestCaseResult testCaseResult) { if (expectedList.Count != testList.Count) { testCaseResult.ErrorMessages.Add(String.Format( "Returned list of {0} is an unexpected size {1} vs {2}", @@ -696,13 +737,18 @@ private static void CompareKeyValuePairLists( /// /// If true, will copy the template file to an XML file if it /// doesn't exist. If false, delete the XML file if it exists. - private static void UpdateAdditionalDependenciesFile(bool addDependencyFile) { + /// Name of the template file (without extension) to + /// create an XML from. + private static void UpdateAdditionalDependenciesFile( + bool addDependencyFile, + string filename=ADDITIONAL_DEPENDENCIES_FILENAME) { string currentDirectory = Directory.GetCurrentDirectory(); - string editorPath = Path.Combine(currentDirectory, "Assets/PlayServicesResolver/Editor/"); + string editorPath = Path.Combine(currentDirectory, + "Assets/ExternalDependencyManager/Editor/"); - string templateFilePath = Path.Combine(editorPath, ADDITIONAL_DEPENDENCIES_FILENAME + + string templateFilePath = Path.Combine(editorPath, filename+ ".template"); - string xmlFilePath = Path.Combine(editorPath, ADDITIONAL_DEPENDENCIES_FILENAME + ".xml"); + string xmlFilePath = Path.Combine(editorPath, filename+ ".xml"); if (addDependencyFile && !File.Exists(xmlFilePath)) { if (!File.Exists(templateFilePath)) { UnityEngine.Debug.LogError("Could not find file: " + templateFilePath); @@ -737,13 +783,14 @@ private static void UpdateAdditionalDependenciesFile(bool addDependencyFile) { private static void Resolve(string androidBuildSystem, bool exportProject, string expectedAssetsDir, string targetAbis, ICollection filesToIgnore, - TestCase testCase, Action testCaseComplete, + IntegrationTester.TestCase testCase, + Action testCaseComplete, bool synchronous = false) { // Set the Android target ABIs. - AndroidAbisCurrentString = targetAbis; + GooglePlayServices.AndroidAbis.CurrentString = targetAbis; // Try setting the build system if this version of Unity supports it. if (!GradleBuildSupported && androidBuildSystem == "Gradle") { - testCaseComplete(new TestCaseResult(testCase) { + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { Skipped = true, ErrorMessages = new List { "Unity version does not support Gradle builds." @@ -755,7 +802,7 @@ private static void Resolve(string androidBuildSystem, bool exportProject, ANDROID_BUILD_SYSTEM, StringToAndroidBuildSystemValue(androidBuildSystem)) && GetEditorUserBuildSettingsProperty( ANDROID_BUILD_SYSTEM, androidBuildSystem).ToString() == androidBuildSystem)) { - testCaseComplete(new TestCaseResult(testCase) { + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { ErrorMessages = new List { String.Format("Unable to set AndroidBuildSystem to {0}.", androidBuildSystem) @@ -767,7 +814,7 @@ private static void Resolve(string androidBuildSystem, bool exportProject, if (!(SetEditorUserBuildSettingsProperty(EXPORT_ANDROID_PROJECT, exportProject) && (bool)GetEditorUserBuildSettingsProperty(EXPORT_ANDROID_PROJECT, exportProject) == exportProject)) { - testCaseComplete(new TestCaseResult(testCase) { + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { ErrorMessages = new List { String.Format("Unable to set Android export project to {0}.", exportProject) @@ -777,32 +824,27 @@ private static void Resolve(string androidBuildSystem, bool exportProject, // Resolve dependencies. Action completeWithResult = (bool complete) => { - ExecuteTestCase( + IntegrationTester.Runner.ExecuteTestCase( testCase, () => { - testCaseComplete(new TestCaseResult(testCase) { + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { ErrorMessages = ValidateAndroidResolution(expectedAssetsDir, complete, filesToIgnore) }); }, true); }; if (synchronous) { - bool success = (bool)Google.VersionHandler.InvokeStaticMethod( - AndroidResolverClass, "ResolveSync", args: new object[] { true }, - namedArgs: null); + bool success = PlayServicesResolver.ResolveSync(true); completeWithResult(success); } else { - Google.VersionHandler.InvokeStaticMethod( - AndroidResolverClass, "Resolve", args: null, - namedArgs: new Dictionary() { - {"resolutionCompleteWithResult", completeWithResult} - }); + PlayServicesResolver.Resolve(resolutionCompleteWithResult: completeWithResult); } } /// /// Resolve for Gradle using a template .gradle file. /// + /// Gradle template to use. /// Directory that contains the assets expected from the /// resolution step. /// Object executing this method. @@ -811,13 +853,29 @@ private static void Resolve(string androidBuildSystem, bool exportProject, /// project. /// Whether to delete the gradle template before /// testCaseComplete is called. - private static void ResolveWithGradleTemplate(string expectedAssetsDir, - TestCase testCase, - Action testCaseComplete, - IEnumerable otherExpectedFiles = null, - bool deleteGradleTemplate = true) { + /// Set of files to relative to the generatedAssetsDir. + /// Gradle template properties to use. + /// Whether to delete the gradle template + /// properties before testCaseComplete is called. + /// Gradle settings template to use. + /// Whether to delete the gradle settings template + /// before testCaseComplete is called. + private static void ResolveWithGradleTemplate( + string gradleTemplate, + string expectedAssetsDir, + IntegrationTester.TestCase testCase, + Action testCaseComplete, + IEnumerable otherExpectedFiles = null, + bool deleteGradleTemplateProperties = true, + ICollection filesToIgnore = null, + bool deleteGradleTemplate = true, + string gradleTemplateProperties = null, + bool deleteGradleTemplateSettings = true, + string gradleTemplateSettings = null) { var cleanUpFiles = new List(); if (deleteGradleTemplate) cleanUpFiles.Add(GRADLE_TEMPLATE_ENABLED); + if (deleteGradleTemplateProperties) cleanUpFiles.Add(GRADLE_TEMPLATE_PROPERTIES_ENABLED); + if (deleteGradleTemplateSettings) cleanUpFiles.Add(GRADLE_TEMPLATE_SETTINGS_ENABLED); if (otherExpectedFiles != null) cleanUpFiles.AddRange(otherExpectedFiles); Action cleanUpTestCase = () => { foreach (var filename in cleanUpFiles) { @@ -825,9 +883,18 @@ private static void ResolveWithGradleTemplate(string expectedAssetsDir, } }; try { - File.Copy(GRADLE_TEMPLATE_DISABLED, GRADLE_TEMPLATE_ENABLED); - Resolve("Gradle", false, expectedAssetsDir, - null, null, testCase, (TestCaseResult testCaseResult) => { + GooglePlayServices.SettingsDialog.PatchMainTemplateGradle = true; + File.Copy(gradleTemplate, GRADLE_TEMPLATE_ENABLED); + if (gradleTemplateProperties != null) { + GooglePlayServices.SettingsDialog.PatchPropertiesTemplateGradle = true; + File.Copy(gradleTemplateProperties, GRADLE_TEMPLATE_PROPERTIES_ENABLED); + } + if (gradleTemplateSettings != null) { + GooglePlayServices.SettingsDialog.PatchSettingsTemplateGradle = true; + File.Copy(gradleTemplateSettings, GRADLE_TEMPLATE_SETTINGS_ENABLED); + } + Resolve("Gradle", false, expectedAssetsDir, null, filesToIgnore, testCase, + (IntegrationTester.TestCaseResult testCaseResult) => { if (otherExpectedFiles != null) { foreach (var expectedFile in otherExpectedFiles) { if (!File.Exists(expectedFile)) { @@ -840,7 +907,7 @@ private static void ResolveWithGradleTemplate(string expectedAssetsDir, testCaseComplete(testCaseResult); }, synchronous: true); } catch (Exception ex) { - var testCaseResult = new TestCaseResult(testCase); + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); testCaseResult.ErrorMessages.Add(ex.ToString()); cleanUpTestCase(); testCaseComplete(testCaseResult); @@ -852,6 +919,7 @@ private static void ResolveWithGradleTemplate(string expectedAssetsDir, /// This filters all Unity .meta files from the resultant list. /// /// Directory to search. + /// Set of files to relative to the generatedAssetsDir. /// Root path for relative filenames. This should be any directory /// under the specified searchDir argument. If this is null, searchDir is used. /// Dictionary of file paths mapped to relative file paths. @@ -890,9 +958,7 @@ private static string ExtractZip(string zipFile, List failureMessages) { Directory.CreateDirectory(outputDir); // This uses reflection to access an internal method for testing purposes. // ExtractZip is not part of the public API. - bool successful = (bool)AndroidResolverClass.GetMethod( - "ExtractZip", BindingFlags.Static | BindingFlags.NonPublic).Invoke( - null, new object[]{ zipFile, null, outputDir }); + bool successful = PlayServicesResolver.ExtractZip(zipFile, null, outputDir, false); if (!successful) { failureMessages.Add(String.Format("Unable to extract {0} to {1}", zipFile, outputDir)); @@ -902,20 +968,6 @@ private static string ExtractZip(string zipFile, List failureMessages) { return outputDir; } - /// - /// In .gradle file contents, replace file:///PROJECT_DIR URIs with the absolute path to the - /// project directory. - /// - /// Name of the file. - /// Contents of the file to search and replace. - private static string ReplaceFileProjectDirUri(string filename, string contents) { - if (filename.ToLower().EndsWith(".gradle")) { - return contents.Replace("file:///PROJECT_DIR", - "file:///" + Path.GetFullPath(".").Replace("\\", "/")); - } - return contents; - } - /// /// Compare the contents of two directories. /// @@ -998,19 +1050,18 @@ private static List CompareDirectoryContents(string expectedAssetsDir, string expectedContentsAsString = "(binary)"; string resolvedContentsAsString = expectedContentsAsString; string resolvedExtension = Path.GetExtension(resolvedFile).ToLower(); - foreach (var extension in new[] { ".xml", ".txt", ".gradle" }) { + foreach (var extension in new[] { ".xml", ".txt", ".gradle", ".properties" }) { if (resolvedExtension == extension) { displayContents = true; break; } } if (displayContents) { - expectedContentsAsString = ReplaceFileProjectDirUri( - expectedFile, - System.Text.Encoding.Default.GetString(expectedContents)); - resolvedContentsAsString = ReplaceFileProjectDirUri( - expectedFile, - System.Text.Encoding.Default.GetString(resolvedContents)); + // Compare ignoring leading and trailing whitespace. + expectedContentsAsString = + System.Text.Encoding.Default.GetString(expectedContents).Trim(); + resolvedContentsAsString = + System.Text.Encoding.Default.GetString(resolvedContents).Trim(); differs = expectedContentsAsString != resolvedContentsAsString; } if (differs) { @@ -1051,3 +1102,5 @@ private static List ValidateAndroidResolution(string expectedAssetsDir, return failureMessages; } } + +} diff --git a/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/AndroidResolverTests.asmdef b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/AndroidResolverTests.asmdef new file mode 100644 index 00000000..1df88e66 --- /dev/null +++ b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/AndroidResolverTests.asmdef @@ -0,0 +1,25 @@ +{ + "name": "Google.AndroidResolverTests", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Google.VersionHandler.dll", + "Google.VersionHandlerImpl.dll", + "Google.JarResolver.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/source/JarResolverTests/src/Google.JarResolver.Tests/DependencyTests.cs b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/src/Google.JarResolver.Tests/DependencyTests.cs similarity index 100% rename from source/JarResolverTests/src/Google.JarResolver.Tests/DependencyTests.cs rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/src/Google.JarResolver.Tests/DependencyTests.cs diff --git a/source/JarResolverTests/src/Google.JarResolver.Tests/PlayServicesSupportTests.cs b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/src/Google.JarResolver.Tests/PlayServicesSupportTests.cs similarity index 100% rename from source/JarResolverTests/src/Google.JarResolver.Tests/PlayServicesSupportTests.cs rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/src/Google.JarResolver.Tests/PlayServicesSupportTests.cs diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.aar b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.aar similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.aar rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.aar diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.pom b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.pom similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.pom rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/7.0.0/artifact-7.0.0.pom diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.aar b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.aar similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.aar rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.aar diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.pom b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.pom similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.pom rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.1.0/artifact-8.1.0.pom diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.aar b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.aar similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.aar rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.aar diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.pom b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.pom similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.pom rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/8.2.0-alpha/artifact-8.2.0-alpha.pom diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/artifact/maven-metadata.xml b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/maven-metadata.xml similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/artifact/maven-metadata.xml rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/artifact/maven-metadata.xml diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.aar b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.aar similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.aar rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.aar diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.pom b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.pom similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.pom rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/0.9/subdep-0.9.pom diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.aar b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.aar similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.aar rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.aar diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.pom b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.pom similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.pom rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/1.1.0/subdep-1.1.0.pom diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/subdep/maven-metadata.xml b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/maven-metadata.xml similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/subdep/maven-metadata.xml rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/subdep/maven-metadata.xml diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.aar b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.aar similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.aar rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.aar diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.pom b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.pom similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.pom rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/transdep/1.0.0/transdep-1.0.0.pom diff --git a/source/JarResolverTests/testData/extras/google/m2repository/test/transdep/maven-metadata.xml b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/transdep/maven-metadata.xml similarity index 100% rename from source/JarResolverTests/testData/extras/google/m2repository/test/transdep/maven-metadata.xml rename to source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/testData/extras/google/m2repository/test/transdep/maven-metadata.xml diff --git a/source/ExportUnityPackage/export_unity_package.py b/source/ExportUnityPackage/export_unity_package.py new file mode 100755 index 00000000..b8f16b5a --- /dev/null +++ b/source/ExportUnityPackage/export_unity_package.py @@ -0,0 +1,3559 @@ +#!/usr/bin/python +# +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +r"""A script to build Unity packages without Unity. + +This script enables plugins and assets created for Unity to be packaged into +a Unity package which can be loaded in Unity and supports the appropriate meta +data. The script takes a config file and the root directory of the assets +to be packed. + +The config supports multiple Unity package definitions, and each contains +an inclusive set of files with wildcard support grouped by platform settings. + +Example usage: + export_unity_package.py --config_file=exports.json \ + --guids_file=guids.json \ + --plugins_version="1.0.0" \ + --assets_dir="/tmp/unityBundle" + +The json file should have the following format: +{ + "packages": [ + { + # Name of the Unity package to export. + "name": "yourpackage.unitypackage", + + # Whether this package should be exported for the sections enabled. + # If this is empty the package will always be built. If this + # specifies a list of sections it will only be built if the + # enabled_sections flag contains the enabled sections in this list. + "sections": ["some_section"], + + # Files to import into the package. + "imports": [ + { + # Whether this package should be exported for the sections + # enabled. + # If this is empty the package will always be built. If this + # specifies a list of sections it will only be built if the + # enabled_sections flag contains the enabled sections in + # this list. + "sections": ["some_section"], + + # How / when to import (load) the file in Unity. + # * PluginImporter specifies the file should be imported as + # a C# DLL for the platforms specified by the "platforms" + # field. + # * DefaultImporter specifies that the file should be + # imported using Unity's default settings for the file + # type derived from the file extension. + # This field defaults to "DefaultImporter". + "importer": "PluginImporter", + + # Platforms targeted when the PluginImporter is used. + # Can be a list containing any Unity platform name e.g: + # * Any: Meta platform that targets all platforms. + # * Editor: Unity editor. + # * Standalone: Meta platform that targets all desktop + # platforms including the editor. + # * Android + # * iOS + # * tvOS + "platforms": ["Editor", "Standalone", "Android", "iOS"], + + # CPUs supported by standalone or editor platforms when the + # "PluginImporter" is the importer and platforms contains + # one of "Standalone", "LinuxUniversal", "OSXUniversal" + # or "Editor". + "cpu": "AnyCPU", # (or "x86" or "x86_64") + + # Labels to apply to the asset. These are used to find + # assets quickly in the asset database and change import + # setting via plugins like the Play Services Resolver. + "labels": [ + "gvh", + ... + ], + + # Asset metadata YAML to override the existing metadata for + # the file. This should either be a string containing YAML + # or a JSON dictionary. + # For example, the following uses a JSON dictionary to + # disable the plugin for the "Any" platform. + "override_metadata": { + "PluginImporter": { + "platformData": { + "Any": { + "enabled": 0 + } + } + }, + + # Asset metadata YAML to override the existing metadata for + # the file for Unity Package Manager package. This should + # either be a string containing YAML or a JSON dictionary. + # For example, the following uses a JSON dictionary to + # enable the plugin for the "Editor" platform for UPM + # package. + "override_metadata_upm": { + "PluginImporter": { + "platformData": { + "Editor": { + "enabled": 1 + } + } + }, + + # Files to import with the importer and label settings + # applied. + # Each item in this list can be one of the following: + # - Filename: Includes just this file. + # - Directory: Recursively includes the directory. + # - Unix shell-style wildcard (glob): Includes all files + # matching the pattern. + "paths": [ + "Firebase/Plugins/App.dll", + ... + ] + }, + ... + ], + # Transitively includes all files from the set of packages specified + # by this list. + "includes": [ "anotherpackage.unitypackage" ], + + # List of regular expression strings which exclude files included + # in this plugin. This applies to this plugin if it's exported and + # all plugins that depend upon it. + "exclude_paths": [ + "Firebase/Samples/Auth/.*", + ], + + # Whether to export this package (enabled by default). + "export": 1, + + # Path of the manifest in the package with the basename of the + # manifest file. If a path isn't specified, a manifest isn't + # generated. + # e.g + # My/Cool/ShaderToolkit + # would be expanded to... + # My/Cool/${package_name}_v${version}_manifest.txt + # + # ${package_name} is derived from the output filename and + # ${version} is specified via the command line --plugins_version + # argument. + "manifest_path": "Firebase/Editor/FirebaseAnalytics", + + # Path to the readme document. The file must be included through + # FLAGS.assets_dir, FLAGS.assets_zip or FLAG.asset_file, and is not + # required to be in "imports" section. + "readme": "path/to/a/Readme.md", + + # Path to the changelog document. The file must be included through + # FLAGS.assets_dir, FLAGS.assets_zip or FLAG.asset_file, and is not + # required to be in "imports" section. + "changelog": "path/to/a/Changelog.md", + + # Path to the license document. The file must be included through + # FLAGS.assets_dir, FLAGS.assets_zip or FLAG.asset_file, and is not + # required to be in "imports" section. + "license": "path/to/a/License.md", + + # Path to the documents. The path can be a specific file or a folder + # containing index.md. The file/folder must be included through + # FLAGS.assets_dir, FLAGS.assets_zip or FLAG.asset_file, and is not + # required to be in "imports" section. + "documentaiton": "path/to/a/Document.md", + + # Common package information used to generate package manifest. + # Required if "export_upm" is 1 + "common_manifest": { + # Package name used in the manifest file. Required if + # "export_upm" is 1. + "name": "com.google.firebase.app", + + # Display name for the package. Optional. + "display_name": "Firebase App (Core)", + + # Description for the package. Optional. + # This can be a single string or a list of strings which will be + # joined into single string for manifest. + "description": "This is core library for Firebase", + "description": [ "This is core library ", "for Firebase" ], + + # A list of keywords for the package. Potentially used for + # filtering or searching. Optional. + # Add "vh-name:legacy_manifest_name" to link this package to + # a renamed package imported as an asset package. + # Note that this script will automatically add + # "vh-name:current_package_name" to keywords. + "keywords": [ "Google", "Firebase", "vh-name:MyOldName"], + + # Author information for the package. Optional. + "author": { + "name" : "Google Inc", + "email" : "someone@google.com", + "url": "https://firebase.google.com/" + } + }, + + # Whether to export this package for Unity Package Manager, i.e. + # .tgz tarball (disabled by default) + "export_upm": 0, + + # Package configuration for Unity Package Manager package. Optional. + "upm_package_config": { + # Manifest information for package.json used by Unity Package + # Manager. Optional. + "manifest" : { + # This defines the package's minimum supported Unity version + # in the form "major.minor", for example "2019.1". The + # minimum valid version here is "2017.1". Optional. + "unity": "2017.1", + + # A map containing this package's additional dependencies + # where the keys are package names and the values are + # specific versions, e.g. "1.2.3". This script will also + # automatically includes packages listed in "includes", if + # it is set to export for UPM. + "dependencies": { + "com.some.third-party-package": "1.2.3" + } + } + }, + }, + ... + ], + + # Optional build configurations for the project. + # All packages in the project are exported for each build configuration + # listed in this section. + "builds": [ + { + # Name of this build config for logging purposes. + "name": "debug", + + # Whether this build config should be executed for the sections enabled. + # If this is empty, it will always be executed. + "sections": ["debug"], + + # Sections that should be enabled when exporting packages with this + # build config. This set of sections are added to the sections + # specified on the command line before packages are exported. + "enabled_sections": ["early_access"], + + # List of regular expressions and replacement strings applied to + # package names before they're exported. + # For example: + # { "match": "foo(.*)\\.bar", "replacement": "foo\\1Other.bar" } + # Changes the package name "foo123.bar" to "foo123Other.bar". + "package_name_replacements": [ + { + "match": "(.*)(\\.unitypackage)", + "replacement": "\\1EarlyAccess\\2" + }, + ] + }, + ... + ] +} +""" + +import collections +import copy +import glob +import gzip +import json +import os +import platform +import re +import shutil +import stat +import subprocess +import sys +import tarfile +import tempfile +import traceback +import zipfile +from absl import app +from absl import flags +from absl import logging +import packaging.version +import yaml + +FLAGS = flags.FLAGS + +flags.DEFINE_string("config_file", None, ("Config file that describes how to " + "pack the unity assets.")) +flags.DEFINE_string("guids_file", None, "Json file with stable guids cache.") +flags.DEFINE_string("plugins_version", None, "Version of the plugins to " + "package.") +flags.DEFINE_boolean("use_tar", True, "Whether to use the tar command line " + "application, when available, to generate archives rather " + "than Python's tarfile module. NOTE: On macOS tar / gzip " + "generate Unity compatible but non-reproducible archives.") +flags.DEFINE_boolean( + "enforce_semver", True, "Whether to enforce semver (major.minor.patch) for" + "plugins_version. This is required to build UPM package.") +flags.DEFINE_multi_string("assets_dir", ".", "Directory containing assets to " + "package.") +flags.DEFINE_multi_string("assets_zip", None, "Zip files containing assets to " + "package.") +flags.DEFINE_multi_string("asset_file", None, + "File to copy in a directory to search for assets. " + "This is in the format " + "'input_filename:asset_filename' where " + "input_filename if the path to the file to copy and " + "asset_filename is the path to copy to in directory " + "to stage assets.") +flags.DEFINE_integer("timestamp", 1480838400, # 2016-12-04 + "Timestamp to use for each file. " + "Set to 0 to use the current time.") +flags.DEFINE_string("owner", "root", + "Username of file owner in each generated package.") +flags.DEFINE_string("group", "root", + "Username of file group in each generated package.") +flags.DEFINE_string("output_dir", "output", + "Directory to write the resulting Unity package files.") +flags.DEFINE_string("output_zip", None, "Zip file to archive the output Unity " + "packages.") +flags.DEFINE_boolean( + "output_upm", False, "Whether output packages as tgz for" + "Unity Package Manager.") +flags.DEFINE_boolean("output_unitypackage", True, "Whether output packages as " + "asset packages.") +flags.DEFINE_multi_string("additional_file", None, + "Additional file in the format " + "'input_filename:output_filename', which copies the " + "specified input_filename to output_filename under " + "the output_dir. This can be used to store " + "additional files in the output directory or zip " + "file. If the ':output_filename' portion of the " + "argument isn't specified, the file will be written " + "to the same path as the specified input_filename " + "under the output_dir.") +flags.DEFINE_spaceseplist( + "enabled_sections", None, + ("List of sections to include in the set of packages. " + "Package specifications that do not specify any sections are always " + "included.")) + +# Default metadata for all Unity 5.3+ assets. +DEFAULT_METADATA_TEMPLATE = collections.OrderedDict( + [("fileFormatVersion", 2), + ("guid", None), # A unique GUID *must* be specified for all assets. + ("labels", None), # Can optionally specific a list of asset label strings. + ("timeCreated", 0)]) + +# A minimal set of Importer meta data. +# +# This importer is used if nothing more specific is needed and Unity can often +# infer the correct meta data using this by directory structure. This method is +# used if the json import group's "importer" field is "DefaultImporter". +DEFAULT_IMPORTER_DATA = [("userData", None), + ("assetBundleName", None), + ("assetBundleVariant", None)] +DEFAULT_IMPORTER_METADATA_TEMPLATE = collections.OrderedDict( + [("DefaultImporter", collections.OrderedDict(DEFAULT_IMPORTER_DATA))]) + +DEFAULT_FOLDER_METADATA_TEMPLATE = collections.OrderedDict([ + ("folderAsset", True), + ("DefaultImporter", collections.OrderedDict(DEFAULT_IMPORTER_DATA)) +]) + +PLATFORM_SETTINGS_DISABLED = [("enabled", 0)] +DEFAULT_PLATFORM_SETTINGS_EMPTY_DISABLED = collections.OrderedDict( + PLATFORM_SETTINGS_DISABLED + + [("settings", {})]) + +DEFAULT_PLATFORM_SETTINGS_DISABLED = collections.OrderedDict( + PLATFORM_SETTINGS_DISABLED + + [("settings", collections.OrderedDict( + [("CPU", "AnyCPU")]))]) + +DEFAULT_PLATFORM_SETTINGS_EDITOR = collections.OrderedDict( + PLATFORM_SETTINGS_DISABLED + + [("settings", collections.OrderedDict( + [("CPU", "AnyCPU"), + ("DefaultValueInitialized", True), + ("OS", "AnyOS")]))]) + +# When desktop platforms are disabled Unity expects the CPU to be set to None. +DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE = collections.OrderedDict( + PLATFORM_SETTINGS_DISABLED + + [("settings", collections.OrderedDict( + [("CPU", "None")]))]) + +DEFAULT_PLATFORM_SETTINGS_DISABLED_IOS = collections.OrderedDict( + PLATFORM_SETTINGS_DISABLED + + [("settings", collections.OrderedDict( + [("CompileFlags", None), + ("FrameworkDependencies", None)]))]) + +DEFAULT_PLATFORM_SETTINGS_DISABLED_TVOS = collections.OrderedDict( + PLATFORM_SETTINGS_DISABLED + + [("settings", collections.OrderedDict( + [("CompileFlags", None), + ("FrameworkDependencies", None)]))]) + +PLUGIN_IMPORTER_METADATA_TEMPLATE = collections.OrderedDict( + [("PluginImporter", collections.OrderedDict( + [("serializedVersion", 1), + ("iconMap", {}), + ("executionOrder", {}), + ("isPreloaded", 0), + ("platformData", collections.OrderedDict( + [("Android", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED)), + ("Any", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_EMPTY_DISABLED)), + ("Editor", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_EDITOR)), + ("Linux", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("Linux64", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("LinuxUniversal", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("OSXIntel", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("OSXIntel64", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("OSXUniversal", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("Web", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_EMPTY_DISABLED)), + ("WebStreamed", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_EMPTY_DISABLED)), + ("Win", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("Win64", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_CPU_NONE)), + ("WindowsStoreApps", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED)), + ("iOS", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_IOS)), + ("tvOS", copy.deepcopy( + DEFAULT_PLATFORM_SETTINGS_DISABLED_TVOS)), + ])) + ] + DEFAULT_IMPORTER_DATA)) + ]) + +# Map of platforms to targets. +# Unity 5.6+ metadata requires a tuple of (target, name) for each platform. +# This ignores targets like "Facebook" which overlap with more common targets +# like "Standalone". +PLATFORM_TARGET_BY_PLATFORM = { + "Any": "Any", + "Editor": "Editor", + "Android": "Android", + "Linux": "Standalone", + "Linux64": "Standalone", + "LinuxUniversal": "Standalone", + "OSXIntel": "Standalone", + "OSXIntel64": "Standalone", + "OSXUniversal": "Standalone", + "Web": "WebGL", + "WebStreamed": "", + "Win": "Standalone", + "Win64": "Standalone", + "WindowsStoreApps": "Windows Store Apps", + "iOS": "iPhone", + "tvOS": "tvOS", +} + +# Alias for standalone platforms specified by the keys of +# CPU_BY_DESKTOP_PLATFORM. +STANDALONE_PLATFORM_ALIAS = "Standalone" +# Maps architecture specific platform selections to "universal" +# platforms. Universal in Unity doesn't really mean that it can target any +# architecture, instead it is master flag that controls whether the asset is +# enabled for export. +ARCH_SPECIFIC_TO_UNIVERSAL_PLATFORM = { + "Linux": "LinuxUniversal", + "Linux64": "LinuxUniversal", + "OSXIntel": "OSXUniversal", + "OSXIntel64": "OSXUniversal", +} + +# Set of supported platforms for each shared library extension. +PLATFORMS_BY_SHARED_LIBRARY_EXTENSION = { + ".so": set(["Any", "Editor", "Linux", "Linux64", "LinuxUniversal"]), + ".bundle": set(["Any", "Editor", "OSXIntel", "OSXIntel64", "OSXUniversal"]), + ".dll": set(["Any", "Editor", "Win", "Win64"]) +} + +# Desktop platform to CPU mapping. +CPU_BY_DESKTOP_PLATFORM = { + "Linux": "x86", + "OSXIntel": "x86", + "Win": "x86", + "Linux64": "x86_64", + "OSXIntel64": "x86_64", + "Win64": "x86_64", + "LinuxUniversal": "AnyCPU", + "OSXUniversal": "AnyCPU", +} +# CPU to desktop platform mapping. +DESKTOP_PLATFORMS_BY_CPU = { + "x86": [p for p, c in CPU_BY_DESKTOP_PLATFORM.items() if c == "x86"], + "x86_64": [p for p, c in CPU_BY_DESKTOP_PLATFORM.items() if c == "x86_64"], + "AnyCPU": CPU_BY_DESKTOP_PLATFORM.keys(), +} + +# Unity 5.6 and beyond modified the PluginImporter format such that platforms +# are enabled using a list of dictionaries with the keys "first" and "second" +# controlling platform settings. This constant matches the keys in entries of +# the PluginImporter.platformData list. +UNITY_5_6_PLATFORM_DATA_KEYS = ["first", "second"] + +# Prefix for labels that are applied to files managed by the VersionHandler +# module. +VERSION_HANDLER_LABEL_PREFIX = "gvh" +# Prefix for version numbers in VersionHandler filenames and labels. +VERSION_HANDLER_VERSION_FIELD_PREFIX = "version-" +VERSION_HANDLER_MANIFEST_FIELD_PREFIX = "manifest" +# Separator for filenames and fields parsed by the VersionHandler. +VERSION_HANDLER_FIELD_SEPARATOR = "_" +# Prefix for labels that are applied to files managed by the Unity Package +# Manager module. +UPM_RESOLVER_LABEL_PREFIX = "gupmr" +UPM_RESOLVER_MANIFEST_FIELD_PREFIX = "manifest" +# Separator for filenames and fields parsed by the UPM Resolver. +UPM_RESOLVER_FIELD_SEPARATOR = "_" +# Prefix for latest labels that are applied to files managed by the +# VersionHandler module. +VERSION_HANDLER_PRESERVE_LABEL_PREFIX = "gvhp" +VERSION_HANDLER_PRESERVE_MANIFEST_NAME_FIELD_PREFIX = "manifestname-" +VERSION_HANDLER_PRESERVE_EXPORT_PATH_FIELD_PREFIX = "exportpath-" +VERSION_HANDLER_MANIFEST_TYPE_LEGACY = 0 +VERSION_HANDLER_MANIFEST_TYPE_UPM = 1 +# Prefix for canonical Linux library names. +VERSION_HANDLER_LINUXLIBNAME_FIELD_PREFIX = "linuxlibname-" +# Canonical prefix of Linux shared libraries. +LINUX_SHARED_LIBRARY_PREFIX = "lib" +# Extension used by Linux shared libraries. +LINUX_SHARED_LIBRARY_EXTENSION = ".so" +# Path relative to the "Assets" dir of native Linux plugin libraries. +LINUX_SHARED_LIBRARY_PATH = re.compile( + r"^Plugins/(x86|x86_64)/(.*{ext})".format( + ext=LINUX_SHARED_LIBRARY_EXTENSION.replace(".", r"\."))) +# Path components required for native desktop libraries. +SHARED_LIBRARY_PATH = re.compile( + r"(^|/)Plugins/(x86|x86_64)/(.*/|)[^/]+\.(so|dll|bundle)$") +# Prefix of the keywords to be added to UPM manifest to link to legacy manifest. +UPM_KEYWORDS_MANIFEST_PREFIX = "vh-name:" +# Everything in a Unity plugin - at the moment - lives under the Assets +# directory +ASSETS_DIRECTORY = "Assets" +# Extension for asset metadata files. +ASSET_METADATA_FILE_EXTENSION = ".meta" +# Valid version for asset package in form of major.minor.patch(-preview) +VALID_VERSION_RE = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(-preview)?$") + +# Documentation folder and filename for UPM package. +UPM_DOCUMENTATION_DIRECTORY = "Documentation~" +UPM_DOCUMENTATION_FILENAME = "index.md" + +# String and unicode classes used to check types with safe_dict_get_value() +try: + unicode("") # See whether unicode class is available (Python < 3) + STR_OR_UNICODE = [str, unicode] +except NameError: + STR_OR_UNICODE = [str] + unicode = str # pylint: disable=redefined-builtin,invalid-name + +def posix_path(path): + """Convert path separators to POSIX style. + + Args: + path: Path to convert. + + Returns: + Path with POSIX separators, i.e / rather than \\. + """ + return path.replace('\\', '/') + + +class MissingGuidsError(Exception): + """Raised when GUIDs are missing for input files in export_package(). + + Attributes: + missing_guid_paths: List of files missing GUIDs. + """ + + def __init__(self, missing_guid_paths): + """Initialize the instance. + + Args: + missing_guid_paths: List of files missing GUIDs. + """ + self.missing_guid_paths = sorted(list(set(missing_guid_paths))) + super(MissingGuidsError, self).__init__(self.__str__()) + + def __str__(self): + """Retrieves a description of this error.""" + guids_file = FLAGS.guids_file if FLAGS.guids_file else "" + plugins_version = FLAGS.plugins_version if FLAGS.plugins_version else "" + return (("There were asset paths without a known guid. " + "generate guids for these assets:\n\n" + "{gen_guids} " + "--guids_file=\"{guids_file}\" " + "--version=\"{plugins_version}\" \"").format( + gen_guids=os.path.realpath( + os.path.join(os.path.dirname(__file__), "gen_guids.py")), + guids_file=guids_file, plugins_version=plugins_version) + + "\" \"".join(self.missing_guid_paths) + "\"") + + +class DuplicateGuidsError(Exception): + """Raised when GUIDs are duplicated for multiple export paths. + + Attributes: + paths_by_guid: GUIDs that have multiple paths associated with them. + """ + + def __init__(self, paths_by_guid): + self.paths_by_guid = paths_by_guid + super(DuplicateGuidsError, self).__init__(self.__str__()) + + def __str__(self): + """Retrieves a description of this error.""" + return ("Found duplicate GUIDs that map to multiple paths.\n%s" % + "\n".join(["%s --> %s" % (guid, str(sorted(paths))) + for guid, paths in self.paths_by_guid.items()])) + + +class DuplicateGuidsChecker(object): + """Ensures no duplicate GUIDs are present in the project. + + Attributes: + _paths_by_guid: Set of file paths by GUID. + """ + + def __init__(self): + """Initialize this instance.""" + self._paths_by_guid = collections.defaultdict(set) + + def add_guid_and_path(self, guid, path): + """Associate an export path with a GUID. + + Args: + guid: GUID to add to this instance. + path: Path associated with this GUID. + """ + self._paths_by_guid[guid].add(posix_path(path)) + + def check_for_duplicates(self): + """Check the set of GUIDs for duplicate paths. + + Raises: + DuplicateGuidsError: If multiple paths are found for the same GUID. + """ + conflicting_paths_by_guid = dict( + [(guid, paths) + for guid, paths in self._paths_by_guid.items() if len(paths) > 1]) + if conflicting_paths_by_guid: + raise DuplicateGuidsError(conflicting_paths_by_guid) + + +class YamlSerializer(object): + """Loads and saves YAML files preserving the order of elements.""" + + class OrderedLoader(yaml.Loader): + """Overrides the default YAML loader to construct nodes as OrderedDict.""" + _initialized = False + + @classmethod + def initialize(cls): + """Installs the construct_mapping constructor on the Loader.""" + if not cls._initialized: + cls.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, + cls._construct_mapping) + cls._initialized = True + + @staticmethod + def _construct_mapping(loader, node): + """Constructs an OrderedDict from a YAML node. + + Args: + loader: yaml.Loader loading the file. + node: Node being mapped to a python data structure. + + Returns: + OrderedDict for the YAML node. + """ + loader.flatten_mapping(node) + return collections.OrderedDict(loader.construct_pairs(node)) + + class OrderedDumper(yaml.Dumper): + """Overrides the default YAML serializer. + + By default maps items to the OrderedDict structure, None to an empty + strings and disables aliases. + """ + _initialized = False + + @classmethod + def initialize(cls): + """Installs the representers on this class.""" + if not cls._initialized: + # By default map data structures to OrderedDict. + cls.add_representer(collections.OrderedDict, cls._represent_map) + # By default map None to empty strings. + cls.add_representer(type(None), cls._represent_none) + # By default map unicode to strings. + cls.add_representer(unicode, cls._represent_unicode) + cls._initialized = True + + @staticmethod + def _represent_unicode(dumper, data): + """Strip the unicode tag from a yaml dump. + + Args: + dumper: Generates the mapping. + data: Data to generate the representer for. + + Returns: + String mapping for unicode data. + """ + return dumper.represent_scalar(u"tag:yaml.org,2002:str", data) + + @staticmethod + def _represent_map(dumper, data): + """Return a default representer for a map. + + Args: + dumper: Generates the mapping. + data: Data to generate a representor for. + + Returns: + The default mapping for a map. + """ + return dumper.represent_mapping( + yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, data.items()) + + @staticmethod + def _represent_none(dumper, unused_data): + """Return a representer for None that emits an empty string. + + Args: + dumper: Generates the mapping. + unused_data: Unused. + + Returns: + A mapping that returns an empty string for None entries. + """ + return dumper.represent_scalar(u"tag:yaml.org,2002:null", "") + + def ignore_aliases(self, unused_data): + """Disable data structure aliases. + + Returns: + True always. + """ + return True + + def __init__(self, *unused_argv): + """Create the serializer.""" + YamlSerializer.OrderedLoader.initialize() + YamlSerializer.OrderedDumper.initialize() + + def load(self, yaml_string): + """Load yaml from a string into this class. + + Args: + yaml_string: String to load YAML from. + + Returns: + OrderedDict loaded from YAML. + """ + return yaml.load(yaml_string, Loader=YamlSerializer.OrderedLoader) + + def dump(self, data): + """Generate a YAML string from the data in this class. + + Args: + data: Set of Python data structures to dump to YAML. + + Returns: + YAML string representation of this class. + """ + return yaml.dump(data, Dumper=YamlSerializer.OrderedDumper, + default_flow_style=False) + + +def merge_ordered_dicts(merge_into, merge_from): + """Merge ordered dicts. + + Merge nodes of merge_from into merge_into. + + - If a node exists in merge_into and merge_from and they're both a dictionary, + merge them together. + - If a node exists in merge_into and merge_from and both values are lists of + dictionaries where each dictionary contains the keys "first" and "second", + merge the lists using the value of "first" in each dictionary as the merge + key. This allows modification of the platform targeting data structure in + Unity asset metadata. + + In all other cases, replace the node in merge_info with the value from + merge_from. + + Args: + merge_into: OrderedDict instance to merge values into. + merge_from: OrderedDict instance to merge values from. + + Returns: + Value of merge_into. + """ + + def list_contains_dictionaries_with_keys(list_to_query, expected_keys): + """Check a list for dictionaries with exactly the specified keys. + + Args: + list_to_query: List to query. + expected_keys: Keys to search for in each dictionary in the list. + + Returns: + True if the list contains dictionaries with exactly the specified + keys, False otherwise. + """ + list_matches = False + if issubclass(list_to_query.__class__, list): + list_matches = list_to_query and True + for item in list_to_query: + if not (issubclass(item.__class__, dict) and + sorted(item.keys()) == expected_keys): + list_matches = False + break + return list_matches + + if (issubclass(merge_from.__class__, dict) and + issubclass(merge_into.__class__, dict)): + for merge_from_key, merge_from_value in merge_from.items(): + merge_into_value = merge_into.get(merge_from_key) + if merge_into_value is not None: + if (issubclass(merge_into_value.__class__, dict) and + issubclass(merge_from_value.__class__, dict)): + merge_ordered_dicts(merge_into_value, merge_from_value) + continue + if (list_contains_dictionaries_with_keys( + merge_into_value, UNITY_5_6_PLATFORM_DATA_KEYS) and + list_contains_dictionaries_with_keys( + merge_from_value, UNITY_5_6_PLATFORM_DATA_KEYS)): + for merge_from_list_item in merge_from_value: + # Try finding the dictionary to merge based upon the hash of the + # "first" item value. + merged = None + key = str(merge_from_list_item["first"]) + for merge_into_list_item in merge_into_value: + if str(merge_into_list_item["first"]) == key: + merge_ordered_dicts(merge_into_list_item, merge_from_list_item) + merged = merge_into_list_item + break + # If the dictionary wasn't merged, add it to the list. + if not merged: + merge_into_value.append(merge_from_list_item) + continue + merge_into[merge_from_key] = merge_from_value + return merge_into + + +def safe_dict_get_value(tree_node, key, default_value=None, value_classes=None): + """Safely retrieve a value from a node in a tree read from JSON or YAML. + + The JSON and YAML parsers returns nodes that can be container or non-container + types. This method internally checks the node to make sure it's a dictionary + before querying for the specified key. If a default value or value_class are + specified, this method also ensures the returned value is derived from the + same type as default_value or value_type. + + Args: + tree_node: Node to query. + key: Key to retrieve from the node. + default_value: Default value if the key doesn't exist in the node or the + node isn't derived from dictionary. + value_classes: List of expected classes of the key value. If the returned + type does not match one of these classes the default value is returned. + If this is not specified, the class of the default_value is used instead. + + Returns: + Value corresponding to key in the tree_node or default_value if the key does + not exist or doesn't match the expected class. + """ + if not issubclass(tree_node.__class__, dict): + return default_value + + value = tree_node.get(key) + if value is None: + value = default_value + elif default_value is not None or value_classes: + if not value_classes: + value_classes = [default_value.__class__] + if default_value.__class__ == str: + value_classes.append(unicode) + + matches_class = False + for value_class in value_classes: + if issubclass(value.__class__, value_class): + matches_class = True + break + + if not matches_class: + logging.warning("Expected class %s instead of class %s while reading key " + "%s from %s. Will use value %s instead of %s.\n%s", + value_classes, value.__class__, key, tree_node, + default_value, value, "".join(traceback.format_stack())) + value = default_value + return value + + +def safe_dict_set_value(tree_node, key, value): + """Safely set a value to a node in a tree read from JSON or YAML. + + The JSON and YAML parsers returns nodes that can be container or non-container + types. This method internally checks the node to make sure it's a dictionary + before setting for the specified key. If value is None, try to remove key + from tree_node. + + Args: + tree_node: Node to set. + key: Key of the entry to be added to the node. + value: Value of the entry to be added to the node. If None, try to remove + the entry from tree_node. + + Returns: + Return tree_node + """ + if not issubclass(tree_node.__class__, dict): + return tree_node + + if value is None: + if key in tree_node: + del tree_node[key] + else: + tree_node[key] = value + + return tree_node + + +class GuidDatabase(object): + """Reads GUIDs from .meta files and a GUID cache. + + Attributes: + _guids_by_path: Cache of GUIDs by path. + _duplicate_guids_checker: Instance of DuplicateGuidsChecker to ensure no + duplicate GUIDs are present. + """ + + def __init__(self, duplicate_guids_checker, guids_json, + plugin_version): + """Initialize the database with data from the GUIDs database. + + Args: + duplicate_guids_checker: Instance of DuplicateGuidsChecker. + guids_json: JSON dictionary that contains the GUIDs to search for. + See firebase/app/client/unity/gen_guids.py for the format. + This can be None to not initialize the database. + plugin_version: Version to use for GUID selection in the specified JSON. + """ + self._guids_by_path = {} + self._duplicate_guids_checker = duplicate_guids_checker + + if guids_json: + guid_map = safe_dict_get_value(guids_json, plugin_version, + default_value={}) + for filename, guid in guid_map.items(): + self.add_guid(posix_path(filename), guid) + + if plugin_version: + # Aggregate guids for older versions of files. + current_version = packaging.version.Version(plugin_version) + for version in sorted(guids_json, key=packaging.version.Version, + reverse=True): + # Skip all versions after and including the current version. + if packaging.version.Version(version) >= current_version: + continue + # Add all guids for files to the current version. + guids_by_filename = guids_json[version] + for filename in guids_by_filename: + if filename not in guid_map: + self.add_guid(filename, guids_by_filename[filename]) + + def add_guid(self, path, guid): + """Add a GUID for the specified path to the guid_map and GUID checker. + + Args: + path: Path associated with the GUID. + guid: GUID for the asset at the path. + """ + path = posix_path(path) + self._guids_by_path[path] = guid + self._duplicate_guids_checker.add_guid_and_path(guid, path) + + def read_guids_from_assets(self, assets): + """Read GUIDs from a set of metadata files into the database. + + Args: + assets: List of Asset instances to read GUIDs from. + + Raises: + MissingGuidsError: If GUIDs are missing for any of the specified assets. + DuplicateGuidsError: If any of the read GUIDs are duplicates. + """ + missing_guid_paths = [] + for asset in assets: + existing_guid = None + metadata_guid = safe_dict_get_value(asset.importer_metadata, "guid", + value_classes=STR_OR_UNICODE) + try: + existing_guid = self.get_guid(asset.filename_guid_lookup) + except MissingGuidsError: + pass + guid = metadata_guid if metadata_guid else existing_guid + if guid: + self.add_guid(asset.filename_guid_lookup, guid) + else: + missing_guid_paths.append(asset.filename_guid_lookup) + if missing_guid_paths: + raise MissingGuidsError(missing_guid_paths) + self._duplicate_guids_checker.check_for_duplicates() + + def get_guid(self, path): + """Get a GUID for the specified path. + + Args: + path: Asset export path to retrieve the GUID of. + + Returns: + GUID of the asset. + + Raises: + MissingGuidsError: If the GUID isn't found. + """ + path = posix_path(path) + guid = self._guids_by_path.get(path) + if not guid: + raise MissingGuidsError([path]) + return guid + + +def copy_and_set_rwx(source_path, target_path): + """Copy a file/folder and set the target to readable / writeable & executable. + + Args: + source_path: File to copy from. + target_path: Path to copy to. + """ + logging.debug("Copying %s --> %s", source_path, target_path) + file_mode = stat.S_IRWXU | stat.S_IRWXG | stat.S_IROTH | stat.S_IXOTH + + if os.path.isfile(source_path): + target_dir = os.path.dirname(target_path) + if not os.path.exists(target_dir): + os.makedirs(target_dir) + shutil.copy(source_path, target_path) + os.chmod(target_path, file_mode) + elif os.path.isdir(source_path): + shutil.copytree(source_path, target_path) + os.chmod(target_path, file_mode) + for current_dir, directories, filenames in os.walk(target_path): + for directory in [os.path.join(current_dir, d) for d in directories]: + os.chmod(directory, file_mode) + for filename in [os.path.join(current_dir, f) for f in filenames]: + os.chmod(filename, file_mode) + +def version_handler_tag(islabel=True, field=None, value=None): + """Generate a VersionHandler filename or label. + + For more information, see + third_party/unity/unity_jar_resolver/source/VersionHandler + + Args: + islabel: Whether the generated field is a label. If this is false this + simply returns the field and value. + field: Type of the field. + value: Value of the field. + + Returns: + Label string. + """ + label_components = [VERSION_HANDLER_LABEL_PREFIX] if islabel else [] + if field: + if value: + label_components.append( + field + value.replace(VERSION_HANDLER_FIELD_SEPARATOR, "-")) + else: + label_components.append(field) + return VERSION_HANDLER_FIELD_SEPARATOR.join(label_components) + + +def version_handler_filename(filename, field_value_list): + """Generate a VersionHandler filename. + + For more information, see + third_party/unity/unity_jar_resolver/source/VersionHandler + + Args: + filename: Base filename VersionHandler fields are injected into. + field_value_list: List of (field, value) tuples added to the end of the + filename. + + Returns: + Filename for a file managed by the VersionHandler. + """ + filename_prefix, extension = os.path.splitext(filename) + directory, basename = os.path.split(filename_prefix) + components = [os.path.join(directory, + basename.replace(VERSION_HANDLER_FIELD_SEPARATOR, + "-"))] + fields = [] + for field, value in field_value_list: + fields.append(version_handler_tag(islabel=False, field=field, value=value)) + if fields: + components.extend([VERSION_HANDLER_FIELD_SEPARATOR, + VERSION_HANDLER_FIELD_SEPARATOR.join(fields)]) + components.append(extension) + return posix_path("".join(components)) + + +class Asset(object): + """Asset to export. + + Attributes: + _filename: Relative path of the file referenced by this asset. + _filename_guid_lookup: Filename to reference GUID. + _filename_absolute: Absolute path of the file referenced by this asset. + _importer_metadata: OrderedDict of Unity asset metadata used to construct + this class. + _is_folder: Whether this asser is for a folder. + """ + + def __init__(self, + filename, + filename_absolute, + importer_metadata, + filename_guid_lookup=None, + is_folder=False): + """Initialize an asset. + + Args: + filename: Name of the file referenced by this asset. + filename_absolute: Absolute path of the file referenced by this + asset. If this is None, the filename argument is used instead. + importer_metadata: OrderedDict of Unity importer metadata for the file. + filename_guid_lookup: Filename to reference GUID. If this is None, the + filename argument is used instead. + is_folder: Whether this asser is for a folder. + """ + self._filename = filename + self._filename_guid_lookup = filename_guid_lookup or filename + self._filename_absolute = filename_absolute or filename + self._importer_metadata = importer_metadata + self._is_folder = is_folder + + def __eq__(self, other): + """Overrides == operator.""" + if isinstance(other, Asset): + return (self.filename == other.filename and + self.filename_guid_lookup == other.filename_guid_lookup and + self.filename_absolute == other.filename_absolute and + self.importer_metadata == other.importer_metadata and + self.is_folder == other.is_folder) + return False + + def __ne__(self, other): + """Overrides != operator (Required in Python2).""" + return not self.__eq__(other) + + @property + def filename(self): + """Get the name of the file referenced by this asset. + + Returns: + Filename string. + """ + return posix_path(self._filename) + + @property + def filename_absolute(self): + """Get the absolute path of the file referenced by this asset. + + Returns: + Filename string. + """ + return posix_path(self._filename_absolute) + + @property + def filename_guid_lookup(self): + """Get the filename to reference GUID. + + Returns: + Filename string. + """ + return posix_path(self._filename_guid_lookup) + + @property + def is_folder(self): + """Get whether this asset is for a folder. + + Returns: + Boolean whether this asset is for a folder + """ + return self._is_folder + + def __repr__(self): + """Returns a human readable string. + + Returns: + A human readable representation of this object. + """ + return "" % (self.filename, + self.importer_metadata) + + @staticmethod + def add_labels_to_metadata(importer_metadata, labels): + """Add to the labels field of Unity asset metadata OrderedDict. + + Args: + importer_metadata: OrderedDict to modify. + labels: Set of labels to add to asset_metadata. + + Returns: + Modified importer_metadata. + """ + existing_labels = safe_dict_get_value(importer_metadata, "labels", + value_classes=[list]) + new_labels = set(existing_labels or []).union(labels) + if new_labels: + importer_metadata["labels"] = sorted(new_labels) + elif existing_labels is not None: + del importer_metadata["labels"] + return importer_metadata + + @staticmethod + def disable_unsupported_platforms(importer_metadata, filename): + """Disable all platforms that are not supported by a shared library asset. + + If the asset references a shared library, disable all platforms that are + not supported by the asset based upon the asset's filename extension. + + Args: + importer_metadata: Metadata to modify. This is modified in-place. + filename: Name of the asset file. + + Returns: + Modified importer_metadata. + """ + filename = posix_path(os.path.normpath(filename)) + is_shared_library = SHARED_LIBRARY_PATH.search(filename) + if not is_shared_library: + return importer_metadata + + supported_platforms = PLATFORMS_BY_SHARED_LIBRARY_EXTENSION.get( + os.path.splitext(filename)[1]) + if not supported_platforms: + return importer_metadata + + plugin_importer = safe_dict_get_value(importer_metadata, "PluginImporter", + default_value={}) + serialized_version = safe_dict_get_value( + plugin_importer, "serializedVersion", default_value=1) + if serialized_version != 1: + logging.warning("Unsupported platformData version %d. " + "Unable to configure platforms for shared library " + "%s", serialized_version, filename) + return importer_metadata + + platform_data = safe_dict_get_value(plugin_importer, "platformData", + default_value={}) + disable_platforms = sorted(set(platform_data).difference( + set(supported_platforms))) + # Disable the Any platform if any platforms are disabled. + if disable_platforms: + any_config = platform_data.get("Any", collections.OrderedDict()) + any_config["enabled"] = 0 + platform_data["Any"] = any_config + # Disable all platforms in the set. + for current_platform in disable_platforms: + platform_data[current_platform] = copy.deepcopy( + PLUGIN_IMPORTER_METADATA_TEMPLATE[ + "PluginImporter"]["platformData"][current_platform]) + logging.debug("Disabled platforms %s for %s", disable_platforms, filename) + return importer_metadata + + @staticmethod + def platform_data_get_entry(platform_data_item): + """Retrieve a platform entry from an item in the platformData list. + + The PluginImporter.platformData is a list when + PluginImporter.serializedVersion is 2. This list can either contain a + dictionary of platform information dictionaries indexed by "first" / + "second" (Unity 2017+) or a list of dictionaries containing just a "data" + entry (Unity 5.6) which in turn contain a platform information entry with + "first" / "second". This method retrieves the values of the + "first" / "second" tuples from an entry in the platformData list. + + Args: + platform_data_item: Entry in the platformData list. + + Returns: + Tuple of values retrieved from the "first" and "second" entries of the + platform information dictionary. + """ + entry = platform_data_item.get("data", platform_data_item) + return (safe_dict_get_value(entry, "first", + default_value=collections.OrderedDict()), + safe_dict_get_value(entry, "second", + default_value=collections.OrderedDict())) + + @staticmethod + def set_cpu_for_desktop_platforms(importer_metadata): + """Enable CPU(s) for each enabled desktop platform in the metadata. + + Args: + importer_metadata: Metadata to modify. + + Returns: + Modified importer_metadata. + """ + plugin_importer = safe_dict_get_value( + importer_metadata, "PluginImporter", default_value={}) + serialized_version = safe_dict_get_value( + plugin_importer, "serializedVersion", default_value=1) + + if serialized_version == 1: + platform_data = safe_dict_get_value(plugin_importer, "platformData", + default_value={}) + for platform_name, options in platform_data.items(): + if not safe_dict_get_value(options, "enabled", default_value=0): + continue + # Override the CPU of the appropriate platforms. + cpu = CPU_BY_DESKTOP_PLATFORM.get(platform_name) + if not cpu: + continue + settings = options.get("settings", collections.OrderedDict()) + if settings.get("CPU", "None") == "None": + settings["CPU"] = cpu + options["settings"] = settings + else: + platform_data = safe_dict_get_value(plugin_importer, "platformData", + default_value=[]) + for entry in platform_data: + # Parse the platform name tuple from the "first" dictionary. + first, second = Asset.platform_data_get_entry(entry) + platform_tuple = list(first.items())[0] + if len(platform_tuple) < 2: + continue + unused_platform_target, platform_name = platform_tuple + if not second.get("enabled", 0): + continue + # Override the CPU of the appropriate platforms. + cpu = CPU_BY_DESKTOP_PLATFORM.get(platform_name) + if not cpu: + continue + settings = safe_dict_get_value(second, "settings", + default_value=collections.OrderedDict()) + if settings.get("CPU", "None") == "None": + settings["CPU"] = cpu + second["settings"] = settings + return importer_metadata + + @staticmethod + def set_cpu_for_android(importer_metadata, cpu_string): + """Sets the CPU for Android in the metadata if enabled. + + Args: + importer_metadata: Metadata to modify. + cpu_string: The desired CPU string value. + + Returns: + Modified importer_metadata. + """ + plugin_importer = safe_dict_get_value( + importer_metadata, "PluginImporter", default_value={}) + serialized_version = safe_dict_get_value( + plugin_importer, "serializedVersion", default_value=1) + + if serialized_version == 1: + platform_data = safe_dict_get_value(plugin_importer, "platformData", + default_value={}) + for platform_name, options in platform_data.items(): + if not safe_dict_get_value(options, "enabled", default_value=0): + continue + if not cpu_string: + continue + if platform_name == "Android": + settings = options.get("settings", collections.OrderedDict()) + settings["CPU"] = cpu_string + options["settings"] = settings + else: + platform_data = safe_dict_get_value(plugin_importer, "platformData", + default_value=[]) + for entry in platform_data: + # Parse the platform name tuple from the "first" dictionary. + first, second = Asset.platform_data_get_entry(entry) + platform_tuple = list(first.items())[0] + if len(platform_tuple) < 2: + continue + unused_platform_target, platform_name = platform_tuple + if not second.get("enabled", 0): + continue + if not cpu_string: + continue + settings = safe_dict_get_value(second, "settings", + default_value=collections.OrderedDict()) + if platform_name == "Android": + settings["CPU"] = cpu_string + second["settings"] = settings + return importer_metadata + + @staticmethod + def apply_any_platform_selection(importer_metadata): + """Enable / disable all platforms if the "Any" platform is enabled. + + Args: + importer_metadata: Metadata to modify. This is modified in-place. + + Returns: + Modified importer_metadata. + """ + plugin_importer = safe_dict_get_value( + importer_metadata, "PluginImporter", default_value={}) + serialized_version = safe_dict_get_value( + plugin_importer, "serializedVersion", default_value=1) + + if serialized_version == 1: + platform_data = safe_dict_get_value(plugin_importer, "platformData", + default_value={}) + # Check PluginImporter.platformData.Any.enabled for the Any platform + # enabled in Unity 4 & early 5 metadata. + any_enabled = platform_data.get("Any", {}).get("enabled", 0) + if not any_enabled: + return importer_metadata + # If the Any platform is present and either enabled or disabled, enable + # for disable all platforms. + for platform_name, default_config in PLUGIN_IMPORTER_METADATA_TEMPLATE[ + "PluginImporter"]["platformData"].items(): + config = platform_data.get(platform_name) + if config is None: + config = copy.deepcopy(default_config) + config["enabled"] = any_enabled + platform_data[platform_name] = config + else: + # Search for the Any platform and retrieve if it's present and + # enabled / disabled if Unity 5.4+ metadata. + platform_data = safe_dict_get_value(plugin_importer, "platformData", + default_value=[]) + any_enabled = 0 + for entry in platform_data: + first, second = Asset.platform_data_get_entry(entry) + if "Any" in first: + any_enabled = second.get("enabled", 0) + break + if not any_enabled: + return importer_metadata + remaining_platforms = [platform_name for platform_name in ( + PLUGIN_IMPORTER_METADATA_TEMPLATE[ + "PluginImporter"]["platformData"]) if platform_name != "Any"] + new_platform_data = [] + unity_5_6_format = False + # Modify the "enabled" field of each platform in the metadata. + for entry in platform_data: + unity_5_6_format = "data" in entry + # Parse the platform name tuple from the "first" dictionary. + first, second = Asset.platform_data_get_entry(entry) + platform_tuple = list(first.items())[0] + if len(platform_tuple) >= 2: + unused_platform_target, platform_name = platform_tuple + if platform_name in remaining_platforms: + remaining_platforms.remove(platform_name) + entry = copy.deepcopy(entry) + _, second = Asset.platform_data_get_entry(entry) + second["enabled"] = any_enabled + new_platform_data.append(entry) + + # Add all platforms that were not present in the default metadata. + for platform_name in remaining_platforms: + platform_target = PLATFORM_TARGET_BY_PLATFORM.get(platform_name) + entry = collections.OrderedDict([ + ("first", collections.OrderedDict([ + (platform_target, platform_name)])), + ("second", collections.OrderedDict([ + ("enabled", any_enabled)]))]) + if unity_5_6_format: + entry = collections.OrderedDict([("data", entry)]) + new_platform_data.append(entry) + plugin_importer["platformData"] = new_platform_data + return importer_metadata + + @property + def importer_metadata_original(self): + """Get the original metadata section used to import this asset. + + Returns: + Importer section of Unity asset metadata as an OrderedDict. + """ + return self._importer_metadata + + @property + def importer_metadata(self): + """Get the Unity metadata section used to import this asset. + + Returns: + Importer section of Unity asset metadata as an OrderedDict. + """ + # If this is a linux library label the asset with the basename of + # the library so that it can be renamed to work with different versions + # of Unity. + linuxlibname_label_prefix = version_handler_tag( + field=VERSION_HANDLER_LINUXLIBNAME_FIELD_PREFIX) + labels = set() + if not [l for l in labels if l.startswith(linuxlibname_label_prefix)]: + match = LINUX_SHARED_LIBRARY_PATH.match(self._filename) + if match: + basename = os.path.basename(match.group(2)) + # Strip prefix and extension. + if basename.startswith(LINUX_SHARED_LIBRARY_PREFIX): + basename = basename[len(LINUX_SHARED_LIBRARY_PREFIX):] + if basename.endswith(LINUX_SHARED_LIBRARY_EXTENSION): + basename = basename[:-len(LINUX_SHARED_LIBRARY_EXTENSION)] + labels.add(version_handler_tag( + field=VERSION_HANDLER_LINUXLIBNAME_FIELD_PREFIX, + value=basename)) + + # Add gvhp_exportpath- label + labels.add(VERSION_HANDLER_PRESERVE_LABEL_PREFIX + + VERSION_HANDLER_FIELD_SEPARATOR + + VERSION_HANDLER_PRESERVE_EXPORT_PATH_FIELD_PREFIX + + self.filename) + + metadata = copy.deepcopy(self._importer_metadata) + metadata = Asset.add_labels_to_metadata(metadata, labels) + metadata = Asset.disable_unsupported_platforms(metadata, self._filename) + metadata = Asset.apply_any_platform_selection(metadata) + metadata = Asset.set_cpu_for_desktop_platforms(metadata) + return metadata + + @staticmethod + def write_metadata(filename, metadata_list): + """Write asset metadata to a file. + + Args: + filename: Name of the file to write. + metadata_list: List of OrderedDict instances to combine, in the + specified order, to generate the data structure to serialize in YAML to + the metadata file. + """ + output_metadata = collections.OrderedDict() + for metadata in metadata_list: + merge_ordered_dicts(output_metadata, metadata) + # Unity does throws exceptions when encountering empty lists of labels, + # so filter them from the metadata. + if not output_metadata.get("labels") and "labels" in output_metadata: + del output_metadata["labels"] + with open(filename, "wt", encoding='utf-8') as metadata_file: + metadata_file.write(YamlSerializer().dump(output_metadata)) + + def write(self, output_dir, guid, timestamp=-1): + """Write a asset and it's metadata to output_dir. + + Given an asset file, path and metadata, this method creates a Unity plugin + directory structure that facilitates the creation of a .unitypackage + archive. + + This method generates: + * /asset + Copy of the file referenced by asset_filename. + * /asset.meta + Metadata for this asset including `importer_metadata`. + * /pathname + Text file which contains export_path. + + Args: + output_dir: Output directory where the unity package can be staged for + archiving. The final .unitypackage archive can be built from the files + placed here. + guid: The guid to use to pack the asset. This will override the GUID in + any existing metadata. + timestamp: Timestamp to write into the metadata file if a timestamp + does not already exist in importer_metadata. If this argument < 0 the + timestamp is set to the creation time of the source file. + + Returns: + Directory containing the asset, asset.meta and pathname files. + Return None if the asset is for a folder. + + Raises: + RuntimeError: If the asset is exported again with a different path or + asset contents. + """ + # Ignore folder asset when writing to unitypackage + if self.is_folder: + return None + + # Create the output directory. + output_asset_dir = os.path.join(output_dir, guid) + if not os.path.exists(output_asset_dir): + os.makedirs(output_asset_dir) + + # Copy the asset to the output folder. + output_asset_filename = os.path.join(output_asset_dir, "asset") + copy_and_set_rwx(self.filename_absolute, output_asset_filename) + + # Create the "asset.meta" file. + output_asset_metadata_filename = (output_asset_filename + + ASSET_METADATA_FILE_EXTENSION) + + self.create_metadata(output_asset_metadata_filename, guid, timestamp) + + # Create the "pathname" file. + # export_filename is the path of the file when it's imported into a Unity + # project. + with open(os.path.join(output_asset_dir, "pathname"), "wt", + encoding='utf-8') as (pathname_file): + pathname_file.write(posix_path(os.path.join(ASSETS_DIRECTORY, + self.filename))) + return output_asset_dir + + def write_upm(self, output_dir, guid, timestamp=-1): + """Write a asset and it's metadata to output_dir for UPM package. + + Given an asset file, path and metadata, this method creates a Unity custom + package structure that facilitates the creation of a .tgz archive. + + This method generates: + * path/to/asset/asset_filename + Copy of the file referenced by asset_filename, or create folders if this + asset is for a folder. + * path/to/asset/asset_filename.meta + Metadata for this asset. + + Args: + output_dir: Output directory where the unity package can be staged for + archiving. The final .unitypackage archive can be built from the files + placed here. + guid: The guid to use to pack the asset. This will override the GUID in + any existing metadata. + timestamp: Timestamp to write into the metadata file if a timestamp does + not already exist in importer_metadata. If this argument < 0 the + timestamp is set to the creation time of the source file. + + Returns: + Directory containing the asset and asset.meta. + + Raises: + RuntimeError: If the asset is exported again with a different path or + asset contents. + """ + # Create the output directory. + output_asset = os.path.join(output_dir, "package", self.filename) + output_asset_dir = os.path.dirname(output_asset) + + if self.is_folder: + os.makedirs(output_asset) + else: + # Copy the asset to the output folder. + copy_and_set_rwx(self.filename_absolute, output_asset) + + # Create the "path/to/asset/asset_filename.meta" file. + output_asset_metadata_filename = ( + output_asset + ASSET_METADATA_FILE_EXTENSION) + + self.create_metadata(output_asset_metadata_filename, guid, timestamp) + + return output_asset_dir + + def create_metadata(self, filename, guid, timestamp=-1): + """Create metadata file for the asset. + + Args: + filename: Filename for the metadata. + guid: The guid to use to pack the asset. This will override the GUID in + any existing metadata. + timestamp: Timestamp to write into the metadata file if a timestamp does + not already exist in importer_metadata. If this argument < 0 the + timestamp is set to the creation time of the source file, or 0 if this + asset is a folder. + + Raises: + RuntimeError: If the asset is exported again with a different path or + asset contents. + """ + if self.is_folder: + importer_metadata = copy.deepcopy(DEFAULT_FOLDER_METADATA_TEMPLATE) + else: + importer_metadata = self.importer_metadata + + # If a timestamp is specified on the command line override the timestamp + # in the metadata if it isn't set. + if timestamp < 0: + if self.is_folder: + timestamp = 0 + else: + timestamp = int(os.path.getctime(self.filename_absolute)) + timestamp = safe_dict_get_value( + importer_metadata, "timeCreated", default_value=timestamp) + + Asset.write_metadata(filename, [ + DEFAULT_METADATA_TEMPLATE, importer_metadata, + collections.OrderedDict([("guid", guid), ("timeCreated", timestamp)]) + ]) + + @staticmethod + def sorted_by_filename(assets): + """Sort a sequence of assets by filename. + + Args: + assets: Sequence of assets to sort. + + Returns: + List of Asset instances sorted by filename. + """ + return sorted([asset for asset in assets], + key=lambda asset: asset.filename) + + +class ProjectConfigurationError(Exception): + """Raised when there is an error parsing the project configuration.""" + pass + + +class ConfigurationBlock(object): + """Common attributes for all export configuration blocks. + + Attributes: + _json: JSON data for the configuration block. + """ + + def __init__(self, json_data): + """Initialize the configuration block. + + Args: + json_data: JSON dictionary to read common configuration from. + """ + self._json = json_data + + @property + def sections(self): + """Get the sections that enable this block. + + Returns: + List of strings indicating when this block should be enabled. + """ + return set(safe_dict_get_value(self._json, "sections", default_value=[])) + + def get_enabled(self, sections): + """Determine whether this block is enabled given `sections`. + + Args: + sections: Set of sections that are enabled for the block. + + Returns: + True if this block specifies no sections or the specified sections + overlap this block's sections whitelist, False otherwise. + """ + block_sections = self.sections + return ((not block_sections) or + block_sections.intersection(sections)) + + +class AssetConfiguration(ConfigurationBlock): + """Export configuration for a set of assets. + + Attributes: + _package: PackageConfiguration instance this asset group was parsed from. + _json: Dictionary containing the raw asset group configuration. + """ + + def __init__(self, package, asset_json): + """Initialize this asset configuration from JSON. + + Args: + package: PackageConfiguration instance `asset_json` was parsed from. + asset_json: JSON dictionary for this set of assets. + + Raises: + ProjectConfigurationError: If an invalid field is specified. + """ + super(AssetConfiguration, self).__init__(asset_json) + self._package = package + self._asset_json = asset_json + + @property + def importer_metadata(self): + """Get the Unity metadata section used to import this asset. + + Returns: + Importer section of Unity asset metadata as an OrderedDict. + + Raises: + ProjectConfigurationError: If the importer type or the cpu string for a + PluginImporter is invalid. + """ + importer_type = safe_dict_get_value(self._json, "importer", + default_value="DefaultImporter") + importer_metadata = None + if importer_type == "DefaultImporter": + importer_metadata = copy.deepcopy(DEFAULT_IMPORTER_METADATA_TEMPLATE) + elif importer_type == "PluginImporter": + platforms = set(safe_dict_get_value( + self._json, "platforms", default_value=["Editor", "Android", "iOS", + "tvOS", + STANDALONE_PLATFORM_ALIAS])) + cpu_string = safe_dict_get_value(self._json, "cpu", + default_value="AnyCPU") + + # Select standalone platforms. + standalone_platforms = [] + if STANDALONE_PLATFORM_ALIAS in platforms: + standalone_platforms = DESKTOP_PLATFORMS_BY_CPU.get(cpu_string) + if not standalone_platforms: + raise ProjectConfigurationError( + "Unknown cpu type %s for package %s, paths %s" % ( + cpu_string, self._package.name, str(self.paths))) + platforms.remove(STANDALONE_PLATFORM_ALIAS) + # Enable "universal" platforms where applicable. + universal_platforms = set() + for standalone in standalone_platforms: + universal_platform = ARCH_SPECIFIC_TO_UNIVERSAL_PLATFORM.get( + standalone) + if universal_platform: + universal_platforms.add(universal_platform) + platforms = platforms.union(standalone_platforms).union( + universal_platforms) + + # Enable selected platforms. + importer_metadata = copy.deepcopy(PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = importer_metadata["PluginImporter"]["platformData"] + for target_platform in platforms: + platform_data_options = platform_data.get(target_platform, + collections.OrderedDict()) + platform_data[target_platform] = platform_data_options + platform_data_options["enabled"] = 1 + importer_metadata = Asset.set_cpu_for_desktop_platforms( + importer_metadata) + if "Android" in platforms and cpu_string != "AnyCPU": + importer_metadata = Asset.set_cpu_for_android( + importer_metadata, cpu_string) + # Set validateReferences, if requested, which should be either 0 or 1 + validateRef = safe_dict_get_value(self._json, "validateReferences", default_value=2) + if validateRef == 0 or validateRef == 1: + importer_metadata["PluginImporter"]["validateReferences"] = validateRef + else: + raise ProjectConfigurationError( + "Unknown importer type %s for package %s, paths %s" % ( + importer_type, self._package.name, str(self.paths))) + Asset.add_labels_to_metadata(importer_metadata, self.labels) + return importer_metadata + + @property + def labels(self): + """Get the set of asset labels that should be applied to this asset. + + Returns: + Set of asset label strings to apply to the assets. + """ + return set(safe_dict_get_value( + self._json, "labels", default_value=[])).union(self._package.labels) + + @property + def paths(self): + """Get the set of paths to include in this group of assets. + + Returns: + Set of paths to include in this group. + """ + return set(safe_dict_get_value(self._json, "paths", default_value=[])) + + @property + def override_metadata(self): + """Get the metadata used override. + + Returns: + OrderedDict metadata to merge over the metadata in each asset referenced + by this AssetConfiguration instance. + """ + return safe_dict_get_value(self._json, "override_metadata", + default_value=collections.OrderedDict(), + value_classes=[dict]) + + @property + def override_metadata_upm(self): + """Get the metadata used override for UPM package. + + Returns: + OrderedDict metadata to merge over the metadata in each asset referenced + by this AssetConfiguration instance. + """ + return safe_dict_get_value( + self._json, + "override_metadata_upm", + default_value=collections.OrderedDict(), + value_classes=[dict]) + + def find_assets(self, assets_dirs, for_upm=False): + """Find the assets referenced by the `paths` attribute. + + Args: + assets_dirs: List of root directories to search for paths referenced by + this asset group. + for_upm: Whether this is for packaging for Unity Package Manager + + Returns: + List of Asset instances referencing file paths found under root_dir + matching the patterns in the `paths` attribute. All returned paths are + relative to the specified assets_dir. + """ + matching_files = set() + assets_dir_by_matching_file = {} + paths_matching_no_files = [] + for wildcard_path in sorted(self.paths): + found_assets = [] + for assets_dir in assets_dirs: + assets_dir = os.path.normpath(assets_dir) + for path in glob.glob(os.path.join(assets_dir, wildcard_path)): + if os.path.isdir(path): + for current_root, _, files in os.walk(path): + for filename in files: + if not AssetConfiguration._is_metadata_file(filename): + relative_path = os.path.relpath(os.path.join( + current_root, filename), assets_dir) + found_assets.append(relative_path) + matching_files.add(relative_path) + assets_dir_by_matching_file[relative_path] = assets_dir + elif not AssetConfiguration._is_metadata_file(path): + relative_path = os.path.relpath(path, assets_dir) + found_assets.append(relative_path) + matching_files.add(relative_path) + assets_dir_by_matching_file[relative_path] = assets_dir + if not found_assets: + paths_matching_no_files.append(wildcard_path) + if paths_matching_no_files: + logging.warning( + "Package %s references paths that match no files %s", + self._package.name, str(paths_matching_no_files)) + importer_metadata = self.importer_metadata + + assets = [] + # If metadata exists, read it for each file in this group creating a list + # Asset instances that references each filename and associated metadata. + for filename in sorted(matching_files): + assets_dir = assets_dir_by_matching_file[filename] + asset_metadata_filename = os.path.join( + assets_dir, filename + ASSET_METADATA_FILE_EXTENSION) + asset_metadata = copy.deepcopy(importer_metadata) + if os.path.exists(asset_metadata_filename): + existing_asset_metadata = collections.OrderedDict() + with open(asset_metadata_filename, "rt", encoding='utf-8') as ( + asset_metadata_file): + existing_asset_metadata = YamlSerializer().load( + asset_metadata_file.read()) + if existing_asset_metadata: + # If the file already has metadata use it, preserving the labels from + # this instance. + Asset.add_labels_to_metadata(existing_asset_metadata, self.labels) + asset_metadata = existing_asset_metadata + + merge_ordered_dicts(asset_metadata, self.override_metadata) + # Override metadata again using "override_metadata_upm" + if for_upm: + merge_ordered_dicts(asset_metadata, self.override_metadata_upm) + + assets.append(Asset(filename, os.path.join(assets_dir, filename), + asset_metadata)) + + return Asset.sorted_by_filename(assets) + + @staticmethod + def _is_metadata_file(path): + """Returns true if the path references a Unity asset metadata file. + + Args: + path: Path of the file to query. + + Returns: + True if the file is an asset metadata file, False otherwise. + """ + return os.path.splitext(path)[1] == ".meta" + + +class PackageConfiguration(ConfigurationBlock): + """Export configuration for a package. + + Attributes: + _project: ProjectConfiguration instance this package was parsed from. + _json: Dictionary containing the raw package configuration. + """ + + def __init__(self, project, package_json): + """Initialize this object with JSON package data. + + Args: + project: ProjectConfiguration instance this package was parsed from. + package_json: Package dictionary parsed from JSON project data. + + Raises: + ProjectConfigurationError: If the package has no name. + """ + super(PackageConfiguration, self).__init__(package_json) + self._project = project + self._json = package_json + if not safe_dict_get_value(self._json, "name", + value_classes=STR_OR_UNICODE): + raise ProjectConfigurationError("Package found with no name") + + @property + def name(self): + """Get the name of the exported package. + + Returns: + Name of package as string. + """ + return self._json["name"] + + @property + def version(self): + """Get the version of this project. + + Returns: + Version as a string in form of "major.minor.revision" + """ + + return self._project.version + + @property + def tarball_name(self): + """Get the tarball filename for Unity Package Manager package. + + Returns: + Tarball filename as string. + """ + + if not self.common_package_name or not self.version: + return None + return self.common_package_name + "-" + self.version + ".tgz" + + @property + def export_upm(self): + """Get whether to export this package as a Unity Package Manager package. + + Returns: + True if the package should be exported. + """ + return safe_dict_get_value(self._json, "export_upm", default_value=0) == 1 + + @property + def upm_package_config(self): + """Get the package configuration for Unity Package Manager package. + + Returns: + Package configuration as dict. + """ + + return safe_dict_get_value(self._json, "upm_package_config") + + @property + def upm_manifest(self): + """Get the package manifest for Unity Package Manager package. + + Returns: + UPM manifest as dict. + """ + package_info = self.upm_package_config + if not package_info: + return None + + return safe_dict_get_value(package_info, "manifest") + + @property + def common_manifest(self): + """Get common package manifest. + + Returns: + Manifest as dict. + """ + return safe_dict_get_value(self._json, "common_manifest") + + @property + def common_package_name(self): + """Get the common name of this package. + + Returns: + Name of package as string. + """ + package_info = self.common_manifest + if not package_info: + return None + + return safe_dict_get_value(package_info, "name") + + @property + def common_package_display_name(self): + """Get the common display name of this package. + + Returns: + Name of package as string. + """ + package_info = self.common_manifest + if not package_info: + return self.package_name + + return safe_dict_get_value(package_info, "display_name", + default_value=self.package_name) + + @property + def common_package_description(self): + """Get the common description of this package. + + Returns: + Name of package as string. + """ + package_info = self.common_manifest + if not package_info: + return None + + description = safe_dict_get_value(package_info, "description") + if description: + if issubclass(description.__class__, list): + description = "".join(description) + else: + description = str(description) + return description + + def _get_includes(self, recursive=True): + """Get transitively included packages of this package. + + Args: + recursive: Whether to recursively traverse includes. + + Returns: + List of PackageConfiguration instances. + + Raises: + ProjectConfigurationError: If any referenced packages are not present + in the project. + """ + packages_by_name = self._project.packages_by_name + included_packages_by_name = {} + missing_package_names = [] + for include_package_name in safe_dict_get_value(self._json, "includes", + default_value=[]): + package = packages_by_name.get(include_package_name) + if package: + included_packages_by_name[package.name] = package + if recursive: + included_packages_by_name.update( + dict([(pkg.name, pkg) for pkg in package.includes])) + else: + missing_package_names.append(include_package_name) + if missing_package_names: + raise ProjectConfigurationError( + "%s includes missing packages %s" % ( + self.name, missing_package_names)) + return list(included_packages_by_name.values()) + + @property + def includes(self): + """Get transitively included packages of this package. + + Returns: + List of PackageConfiguration instances. + + Raises: + ProjectConfigurationError: If any referenced packages are not present + in the project. + """ + return self._get_includes() + + def check_circular_references_in_includes(self, parents): + """Searches for circular references in includes. + + Args: + parents: List of parents packages including this package. + + Raises: + ProjectConfigurationError: If a circular reference is detected. + """ + parent_names = [parent.name for parent in parents] + if self.name in parent_names: + raise ProjectConfigurationError( + ("Circular package inclusion detected when checking %s which is " + "included by [%s]") % (self.name, " --> ".join(parent_names))) + for include in self._get_includes(recursive=False): + parents.append(self) + include.check_circular_references_in_includes(parents) + del parents[-1] + + @property + def export(self): + """Get whether this package should be exported. + + Returns: + True if the package should be exported to `name`, False otherwise. + """ + return safe_dict_get_value(self._json, "export", default_value=1) == 1 + + @property + def exclude_paths(self): + """Get the set of paths that should be excluded from this package. + + Returns: + Set of regular expressions that match paths which should be excluded from + this package. + """ + return [re.compile(path) + for path in safe_dict_get_value(self._json, "exclude_paths", + default_value=[])] + + def find_assets(self, assets_dirs, check_for_duplicates=True, for_upm=False): + """Find all assets referenced by this package. + + Args: + assets_dirs: List of paths to directories to search for files referenced + by this package. + check_for_duplicates: Whether to raise an exception when duplicate assets + are found with different import settings. + for_upm: Whether this is for packaging for UPM package. + + Returns: + List of Asset instances for all files imported by this package. + + Raises: + ProjectConfigurationError: If more than one file has been included with + different import settings. + """ + # Dictionary of asset lists indexed by package name. + assets_by_package_name = collections.defaultdict(list) + for asset_config in self.imports: + if asset_config.get_enabled(self._project.selected_sections): + assets_by_package_name[self.name].extend( + asset_config.find_assets(assets_dirs, for_upm)) + + # Only add asset from "includes" package if this is not for UPM package. + if not for_upm: + for package in self.includes: + logging.debug("%s including assets from %s", self.name, package.name) + assets_by_package_name[package.name].extend( + package.find_assets(assets_dirs, check_for_duplicates=False)) + + package_and_assets_by_filename = collections.defaultdict(list) + for package_name, assets in assets_by_package_name.items(): + for asset in assets: + package_and_assets_by_filename[asset.filename].append( + (package_name, asset)) + + # Check for duplicate assets with different import settings. + duplicate_assets_errors = [] + for filename, package_and_assets in package_and_assets_by_filename.items(): + if len(package_and_assets) <= 1: + continue + # Look for assets with different import settings for this file. + differing_metadata_packages = set() + previous_package_name = None + previous_asset = None + for package_and_asset in package_and_assets: + package_name, asset = package_and_asset + if previous_package_name and (previous_asset.importer_metadata != + asset.importer_metadata): + differing_metadata_packages.add(previous_package_name) + differing_metadata_packages.add(package_name) + previous_package_name = package_name + previous_asset = asset + + if differing_metadata_packages: + duplicate_assets_errors.append( + ("File %s imported with different import settings in " + "packages %s") % (filename, sorted(differing_metadata_packages))) + if check_for_duplicates and duplicate_assets_errors: + raise ProjectConfigurationError("\n".join(duplicate_assets_errors)) + + # Deduplicate the list of assets that were found. + found_assets = Asset.sorted_by_filename( + [package_and_assets[0][1] for package_and_assets in ( + package_and_assets_by_filename.values())]) + + # Filter out excluded assets. + exclude_paths = self.exclude_paths + if exclude_paths: + assets_with_exclusions_removed = [] + for asset in found_assets: + include = True + for exclude_path in exclude_paths: + if exclude_path.match(asset.filename): + include = False + break + if include: + assets_with_exclusions_removed.append(asset) + found_assets = assets_with_exclusions_removed + + logging.debug("Found assets for package %s: %s", self.name, + [asset.filename for asset in found_assets]) + return found_assets + + @property + def manifest_path(self): + """Get the manifest path of the package in the exported archive. + + Returns: + Path of the directory that will contain the manifest in the exported + archive or None if no manifest should be created. + """ + return safe_dict_get_value(self._json, "manifest_path", + value_classes=STR_OR_UNICODE) + + @property + def package_name(self): + """Get the name of this package configuration. + + Returns: + String as the name without extension. + """ + + return os.path.splitext(self.name)[0] + + @property + def manifest_filename(self): + """Get the filename of the manifest that will be generated by this package. + + Returns: + Filename of the manifest in the exported archive or None if no manifest + should be created. + """ + path = self.manifest_path + version = self._project.version + if path and version: + return version_handler_filename( + os.path.join(path, self.package_name + ".txt"), + [[VERSION_HANDLER_VERSION_FIELD_PREFIX, version], + [VERSION_HANDLER_MANIFEST_FIELD_PREFIX, None]]) + return None + + def get_manifest_metadata(self, + manifest_type=VERSION_HANDLER_MANIFEST_TYPE_LEGACY): + """Get the importer metadata for the manifest generated by this package. + + Args: + manifest_type: Type of the manifest, ex. legacy (.txt) or upm + (package.json) + + Returns: + OrderedDict of importer metadata if a manifest is generated by this + package, None otherwise. + """ + + if not self._project.version: + return None + + if (manifest_type == VERSION_HANDLER_MANIFEST_TYPE_LEGACY and + not self.manifest_filename): + return None + metadata = copy.deepcopy(DEFAULT_METADATA_TEMPLATE) + labels = self.labels + if manifest_type == VERSION_HANDLER_MANIFEST_TYPE_LEGACY: + labels.add( + version_handler_tag(field=VERSION_HANDLER_MANIFEST_FIELD_PREFIX)) + + # Add gvhp_manifestname-0DisplayName + priority = 0 + if self.common_package_display_name: + labels.add(VERSION_HANDLER_PRESERVE_LABEL_PREFIX + + VERSION_HANDLER_FIELD_SEPARATOR + + VERSION_HANDLER_PRESERVE_MANIFEST_NAME_FIELD_PREFIX + + str(priority) + self.common_package_display_name) + priority += 1 + if self.package_name != self.common_package_display_name: + labels.add(VERSION_HANDLER_PRESERVE_LABEL_PREFIX + + VERSION_HANDLER_FIELD_SEPARATOR + + VERSION_HANDLER_PRESERVE_MANIFEST_NAME_FIELD_PREFIX + + str(priority) + self.package_name) + + elif manifest_type == VERSION_HANDLER_MANIFEST_TYPE_UPM: + # gupmr_manifest + labels.add(UPM_RESOLVER_LABEL_PREFIX + UPM_RESOLVER_FIELD_SEPARATOR + + UPM_RESOLVER_MANIFEST_FIELD_PREFIX) + Asset.add_labels_to_metadata(metadata, labels) + + return metadata + + def write_manifest(self, output_dir, assets): + """Write the manifest for this package to the specified directory. + + Args: + output_dir: Directory to write the manifest into. + assets: Assets to write to the manifest, typically returned by + find_assets(). + + Returns: + Asset instance that references the generated manifest. + """ + manifest_filename = self.manifest_filename + if not manifest_filename: + return None + manifest_absolute_path = os.path.join(output_dir, manifest_filename) + manifest_directory = os.path.dirname(manifest_absolute_path) + if not os.path.exists(manifest_directory): + os.makedirs(manifest_directory) + with open(manifest_absolute_path, "wt", encoding='utf-8') as manifest_file: + manifest_file.write( + "%s\n" % "\n".join([posix_path(os.path.join(ASSETS_DIRECTORY, + asset.filename)) + for asset in Asset.sorted_by_filename(assets)])) + # Retrieve a template manifest asset if it exists. + manifest_asset = [asset for asset in assets + if asset.filename == manifest_filename] + return manifest_asset[0] if manifest_asset else ( + Asset(manifest_filename, manifest_absolute_path, + self.get_manifest_metadata(VERSION_HANDLER_MANIFEST_TYPE_LEGACY))) + + def write_upm_manifest(self, output_dir): + """Write UPM manifest for this package to the specified directory. + + Args: + output_dir: Directory to write the manifest into. + + Returns: + Asset instance that references the generated manifest. + + Raises: + ProjectConfigurationError: If this package or its dependencies does not + have package name under common_manifest. + """ + + manifest_filename = "package.json" + manifest_absolute_path = os.path.join(output_dir, manifest_filename) + manifest_directory = os.path.dirname(manifest_absolute_path) + if not os.path.exists(manifest_directory): + os.makedirs(manifest_directory) + + # Compose package.json + package_manifest = {} + package_manifest["name"] = self.common_package_name + if not package_manifest["name"]: + raise ProjectConfigurationError( + "Detected package %s has missing package name under common_manifest" % + (self.name)) + package_manifest["version"] = self.version + + common_manifest = self.common_manifest + # Add manifest info from common_manifest to package.json + if common_manifest: + for common_manifest_key, upm_manifest_key in (("display_name", + "displayName"), + ("keywords", "keywords"), + ("author", "author")): + common_manifest_value = safe_dict_get_value(common_manifest, + common_manifest_key) + safe_dict_set_value(package_manifest, upm_manifest_key, + common_manifest_value) + + # Add description. + safe_dict_set_value(package_manifest, "description", + self.common_package_description) + + # Add additonal keywords to link back to legacy manifest. + if self.export and self.manifest_path: + keywords = safe_dict_get_value(package_manifest, "keywords", + default_value=[]) + keywords.append(UPM_KEYWORDS_MANIFEST_PREFIX + self.package_name) + if self.common_package_display_name != self.package_name: + keywords.append(UPM_KEYWORDS_MANIFEST_PREFIX + + self.common_package_display_name) + package_manifest["keywords"] = keywords + + # Add minimum Unity version + if self.upm_manifest: + safe_dict_set_value(package_manifest, "unity", + safe_dict_get_value(self.upm_manifest, "unity")) + dependencies = safe_dict_get_value( + self.upm_manifest, "dependencies", default_value={}) + else: + dependencies = {} + + # Add additional dependencies from "includes" + missing_deps = [] + include_packages = [ + pkg for pkg in self._get_includes(recursive=False) if pkg.export_upm + ] + for include in include_packages: + if include.common_package_name: + dependencies[include.common_package_name] = include.version + else: + missing_deps.append(include.name) + if missing_deps: + raise ProjectConfigurationError( + ("Detected multiple dependencies by %s has missing package name" + + "\n%s") % (self.name, "\n".join(missing_deps))) + package_manifest["dependencies"] = dependencies + + with open(manifest_absolute_path, "wt", encoding='utf-8') as manifest_file: + json.dump(package_manifest, manifest_file, indent=2) + + return Asset( + manifest_filename, + manifest_absolute_path, + self.get_manifest_metadata(VERSION_HANDLER_MANIFEST_TYPE_UPM), + filename_guid_lookup=os.path.join(self.common_package_name, + manifest_filename)) + + @property + def imports(self): + """Get the AssetConfiguration instances from this package. + + Returns: + List of AssetConfiguration instances. + """ + return [AssetConfiguration(self, import_json) + for import_json in safe_dict_get_value(self._json, "imports", + default_value=[])] + + @property + def labels(self): + """Get the set of labels that should be added to assets in this package. + + Returns: + Set of asset label strings to apply to assets in the package. + """ + label_set = set() + version = self._project.version + if version: + label_set = label_set.union( + [version_handler_tag(), + version_handler_tag(field=VERSION_HANDLER_VERSION_FIELD_PREFIX, + value=version)]) + return label_set + + @staticmethod + def create_archive(archive_filename, input_directory, timestamp): + """Create a .unitypackage archive from a directory. + + Args: + archive_filename: Name of the archive file to create. + input_directory: Directory to archive. + timestamp: Timestamp to apply to the archive and all files in the archive + or -1 to use the current time. + """ + archive_filename = os.path.realpath(archive_filename) + cwd = os.getcwd() + try: + os.chdir(input_directory) + # Create a deterministically ordered set of filesystem entries. + input_filenames = [] + for current_dir, directories, filenames in os.walk(os.path.curdir): + for directory in directories: + input_filenames.append(os.path.join(current_dir, directory)) + for filename in filenames: + input_filenames.append(os.path.join(current_dir, filename)) + input_filenames = sorted( + [os.path.normpath(filename) for filename in input_filenames]) + + archive_dir = os.path.dirname(archive_filename) + if not os.path.exists(archive_dir): + os.makedirs(archive_dir) + + # Create a tar.gz archive. + tar_available = (platform.system() == "Linux" or + platform.system() == "Darwin") + gnu_tar_available = platform.system() == "Linux" + # Whether a reproducible tar.gz is required. + if tar_available and FLAGS.use_tar: + # tarfile is 10x slower than the tar command so use the command line + # tool where it's available and can generate a reproducible archive. + list_filename = os.path.join(tempfile.mkdtemp(), "input_files.txt") + try: + # Create a list of input files to workaround command line length + # limits. + with open(list_filename, "wt", encoding='utf-8') as list_file: + list_file.write("%s\n" % "\n".join(input_filenames)) + + tar_args = ["tar"] + tar_args.extend(["-c", "-z", "-f", archive_filename]) + if gnu_tar_available: + if FLAGS.timestamp: + tar_args.append("--mtime=@%d" % FLAGS.timestamp) + # Hard code the user and group of files in the tar file so that + # the process is reproducible. + tar_args.extend(["--owner=%s" % FLAGS.owner, + "--group=%s" % FLAGS.group]) + tar_args.append("--no-recursion") + else: # Assume BSD tar. + # Set the modification time of each file since BSD tar doesn't have + # an option to override this. + if FLAGS.timestamp: + for filename in input_filenames: + os.utime(filename, (FLAGS.timestamp, FLAGS.timestamp)) + # Don't recurse directories. + tar_args.append("-n") + # Avoid creating mac metadata files with name started with "." + if platform.system() == "Darwin": + tar_args.append("--no-mac-metadata") + tar_args.extend(["-T", list_filename]) + # Disable timestamp in the gzip header. + tar_env = os.environ.copy() + tar_env["GZIP"] = "-n" + subprocess.check_call(tar_args, cwd=input_directory, env=tar_env) + finally: + shutil.rmtree(os.path.dirname(list_filename)) + else: + with open(archive_filename, "wb") as gzipped_tar_file: + with gzip.GzipFile( + # The filename in the archive must end with .tar or .tar.gz for + # Unity to open it on Windows. + os.path.splitext(archive_filename)[0] + ".tar.gz", + fileobj=gzipped_tar_file, mode="wb", + mtime=(timestamp if timestamp >= 0 else None)) as gzip_file: + with tarfile.open(mode="w|", fileobj=gzip_file, + format=tarfile.USTAR_FORMAT, dereference=True, + errorlevel=2) as tar_file: + def reproducible_tarinfo(tarinfo): + """Patch TarInfo so that it generates a reproducible archive. + + Args: + tarinfo: TarInfo to modify. + + Returns: + Modified tarinfo. + """ + tarinfo.mtime = timestamp if timestamp >= 0 else tarinfo.mtime + tarinfo.uid = 0 + tarinfo.gid = 0 + tarinfo.uname = FLAGS.owner + tarinfo.gname = FLAGS.group + return tarinfo + + for filename in input_filenames: + tar_file.add(filename, recursive=False, + filter=reproducible_tarinfo) + + finally: + os.chdir(cwd) + + def write(self, guid_database, assets_dirs, output_dir, timestamp, + package_filename=None): + """Creates a .unitypackage file from a package dictionary. + + Creates a Unity package given the import directory and the package + dictionary containing the groups of imports. + + Args: + guid_database: GuidDatabase instance which contains GUIDs for each + exported asset. + assets_dirs: List of paths to directories to search for files referenced + by this package. + output_dir: Directory where to write the exported .unitypackage. + timestamp: Timestamp to apply to all packaged assets in the archive. + If this value is less than 0, the creation time of each input file is + used instead. + package_filename: Filename to write package to in the output_dir. + + Returns: + Path to the created .unitypackage file. + + Raises: + MissingGuidsError: If GUIDs are missing for input files. + DuplicateGuidsError: If any duplicate GUIDs are present. + ProjectConfigurationError: If files are imported multiple times with + different metadata. + """ + package_filename = package_filename or self.name + unity_package_file = os.path.join(output_dir, package_filename) + + # Create a directory to stage the source assets and assemble the package. + temporary_dir = tempfile.mkdtemp() + try: + generated_assets_dir = os.path.join(temporary_dir, "generated_assets") + os.makedirs(generated_assets_dir) + staging_dir = os.path.join(temporary_dir, "plugin") + os.makedirs(staging_dir) + + logging.info("Packaging %s to %s...", self.name, unity_package_file) + + assets = self.find_assets(assets_dirs) + # If a manifest is enabled, write it into the assets directory. + manifest_asset = self.write_manifest(generated_assets_dir, assets) + if manifest_asset: + assets.append(manifest_asset) + + # Add manifest files for the include packages + for include_package in self.includes: + include_manifest_asset = include_package.write_manifest( + generated_assets_dir, include_package.find_assets(assets_dirs)) + if include_manifest_asset: + assets.append(include_manifest_asset) + + # Populate the GUID database and check for any duplicates. + guid_database.read_guids_from_assets(assets) + + # Process all assets and stage all files for packaging in the staging + # area. + for asset in Asset.sorted_by_filename(assets): + asset_file = asset.write( + staging_dir, guid_database.get_guid(asset.filename_guid_lookup), + timestamp) + logging.info("- Processed %s --> %s", asset.filename, asset_file) + + # Create the .unitypackage file. + PackageConfiguration.create_archive(unity_package_file, staging_dir, + timestamp) + logging.info("Created %s for %s", unity_package_file, self.name) + finally: + shutil.rmtree(temporary_dir) + + return unity_package_file + + def write_upm(self, + guid_database, + assets_dirs, + output_dir, + timestamp, + package_filename=None): + """Creates a .tgz file from a package dictionary. + + Creates a UPM package given the import directory and the package + dictionary containing the groups of imports. + + Args: + guid_database: GuidDatabase instance which contains GUIDs for each + exported asset. + assets_dirs: List of paths to directories to search for files referenced + by this package. + output_dir: Directory where to write the exported .tgz. + timestamp: Timestamp to apply to all packaged assets in the archive. If + this value is less than 0, the creation time of each input file is used + instead. + package_filename: Filename to write package to in the output_dir. + + Returns: + Path to the created .tgz file. + + Raises: + MissingGuidsError: If GUIDs are missing for input files. + DuplicateGuidsError: If any duplicate GUIDs are present. + ProjectConfigurationError: If files are imported multiple times with + different metadata. + """ + package_filename = package_filename or self.tarball_name + unity_package_file = os.path.join(output_dir, package_filename) + + # Create a directory to stage the source assets and assemble the package. + temporary_dir = tempfile.mkdtemp() + try: + generated_assets_dir = os.path.join(temporary_dir, "generated_assets") + os.makedirs(generated_assets_dir) + staging_dir = os.path.join(temporary_dir, "plugin") + os.makedirs(staging_dir) + + logging.info("Packaging %s to %s...", self.name, unity_package_file) + + assets = self.find_assets(assets_dirs, for_upm=True) + + # Create package.json + manifest_asset = self.write_upm_manifest(generated_assets_dir) + if manifest_asset: + assets.append(manifest_asset) + + manifest_asset = self.write_manifest(generated_assets_dir, assets) + if manifest_asset: + assets.append(manifest_asset) + + # Move README.md, CHANGELOG.md and LICENSE.md to root folder. + for config_name, to_location in (("readme", "README.md"), + ("changelog", "CHANGELOG.md"), + ("license", "LICENSE.md")): + from_location = safe_dict_get_value(self._json, config_name) + if from_location: + abs_from_location = find_in_dirs(from_location, assets_dirs) + if abs_from_location: + # Create default metadata + metadata = copy.deepcopy(DEFAULT_METADATA_TEMPLATE) + labels = self.labels + Asset.add_labels_to_metadata(metadata, labels) + + assets.append(Asset( + to_location, + abs_from_location, + metadata, + filename_guid_lookup=os.path.join(self.common_package_name, + to_location), + is_folder=False)) + else: + raise ProjectConfigurationError( + "Cannot find '%s' at '%s' for package '%s'. Perhaps it " + "is not included in assets_dir or assets_zip?" % ( + config_name, from_location, self.name)) + + # Add all folder assets to generate .meta + folders = set() + for asset in assets: + filepath = os.path.os.path.dirname(asset.filename) + while filepath: + folders.add(filepath) + filepath = os.path.os.path.dirname(filepath) + + for folder in folders: + # Same folder from each package needs to have an unique GUID. Therefore, + # filename_guid_lookup is set to "package-name/path/to/folder" + assets.append( + Asset( + folder, + folder, + DEFAULT_METADATA_TEMPLATE, + filename_guid_lookup=os.path.join(self.common_package_name, + folder), + is_folder=True)) + + # Populate the GUID database and check for any duplicates. + guid_database.read_guids_from_assets(assets) + + # Process all assets and stage all files for packaging in the staging + # area. + for asset in Asset.sorted_by_filename(assets): + asset_file = asset.write_upm( + staging_dir, guid_database.get_guid(asset.filename_guid_lookup), + timestamp) + logging.info("- Processed %s --> %s", asset.filename, asset_file) + + # Copy documents to "Documentation~" folder. + # See https://docs.unity3d.com/Manual/cus-layout.html + # All documentation folders and files must not include a .meta file + # otherwise the documentation links in the Unity Package Manager window + # will not work + source_doc = safe_dict_get_value(self._json, "documentation") + if source_doc: + # Try to find the source doc from assets directory + source_doc = find_in_dirs(source_doc, assets_dirs) + if os.path.isfile(source_doc): + # Copy file + target_doc = os.path.join(staging_dir, "package", + UPM_DOCUMENTATION_DIRECTORY, + UPM_DOCUMENTATION_FILENAME) + logging.info("- Copying doc file %s --> %s", source_doc, target_doc) + copy_and_set_rwx(source_doc, target_doc) + elif os.path.isdir(source_doc): + target_doc_dir = os.path.join(staging_dir, "package", + UPM_DOCUMENTATION_DIRECTORY) + # Check if index.md exists + if not os.path.exists(os.path.join(source_doc, + UPM_DOCUMENTATION_FILENAME)): + raise ProjectConfigurationError( + "Cannot find index.md under '%s' for package '%s'. Perhaps it " + "is not included in assets_dir or assets_zip?" % ( + source_doc, self.name)) + + logging.info("- Copying doc folder %s --> %s", + source_doc, target_doc_dir) + copy_and_set_rwx(source_doc, target_doc_dir) + else: + raise ProjectConfigurationError( + "Cannot find documentation at '%s' for package '%s'. Perhaps the " + "file/folder is not included in assets_dir or assets_zip?" % ( + from_location, self.name)) + + # Create the .tgz file. + PackageConfiguration.create_archive(unity_package_file, staging_dir, + timestamp) + logging.info("Created %s for %s", unity_package_file, self.name) + finally: + shutil.rmtree(temporary_dir) + + return unity_package_file + + +class BuildConfiguration(ConfigurationBlock): + """Build configuration used to modify enabled sections and export settings. + + Attributes: + _json: JSON dictionary this configuration is read from. + _replacements: List of (regular_expression, replacement_string) tuples where + regular_expression is a RegexObject instance and replacement_string is + a string that can be passed to re.sub() as part of a replacement. + """ + + def __init__(self, build_json): + """Parse a build configuration. + + Args: + build_json: Dictionary with the configuration to parse. + + Raises: + ProjectConfigurationError: If a package name replacement regular + expression fails to compile. + """ + super(BuildConfiguration, self).__init__(build_json) + self._json = build_json + self._replacements = [] + for match_replacement in (safe_dict_get_value( + self._json, "package_name_replacements", default_value=[])): + match_regexp_string = safe_dict_get_value(match_replacement, "match", + value_classes=STR_OR_UNICODE) + replacement_string = safe_dict_get_value(match_replacement, "replacement", + value_classes=STR_OR_UNICODE) + if match_regexp_string and replacement_string is not None: + try: + self._replacements.append((re.compile(match_regexp_string), + replacement_string)) + except re.error as error: + raise ProjectConfigurationError( + "Failed to compile package name replacement regular expression " + "'%s' for build config %s (%s)" % (match_regexp_string, self.name, + str(error))) + + @property + def name(self): + """Get the name of the build configuration. + + Returns: + Name of build configuration for logging. + """ + return safe_dict_get_value(self._json, "name", default_value="") + + @property + def enabled_sections(self): + """Get the set of sections that should be enabled when exporting. + + Returns: + Set of export section strings that should be enabled when exporting with + this build configuration. + """ + return set(safe_dict_get_value(self._json, "enabled_sections", + default_value=[])) + + @property + def package_name_replacements(self): + """Get the set of replacements to apply to package names. + + Returns: + List of (regular_expression, replacement_string) tuples where + regular_expression is a RegexObject instance and replacement_string is + a string that can be passed to re.sub() as part of a replacement. + """ + return list(self._replacements) + + def apply_package_name_replacements(self, package_name): + """Apply package name replacements to the specified string. + + Args: + package_name: String modified by package name replacements. + + Returns: + Package name modified by the replacements returned by + the package_name_replacements property. + + Raises: + ProjectConfigurationError: If a package name replacement fails. + """ + new_package_name = package_name + for match_regexp, replacement_string in self._replacements: + try: + new_package_name = match_regexp.sub(replacement_string, + new_package_name) + except re.error as error: + raise ProjectConfigurationError( + "Failed to apply package name replacement '%s' --> '%s' from build " + "config %s to package name %s (%s)" % ( + match_regexp.pattern, replacement_string, self.name, + package_name, str(error))) + return new_package_name + + def create_package_name_map(self, package_names_map): + """Create a dictionary which maps existing to replaced package names. + + Args: + package_names_map: Map of package name to filename to replace to generate + a dictionary from. + + Returns: + Dictionary of replacement package names keyed by the original package + name. + + Raises: + ProjectConfigurationError: If multiple package names are mapped to the + same target package name or a package name replacement fails. + """ + new_by_old_package_names = collections.defaultdict(list) + for package_name, filename_to_replace in package_names_map.items(): + new_by_old_package_names[package_name].append( + self.apply_package_name_replacements(filename_to_replace)) + + duplicate_new_names_strings = [] + for old_name, new_names in new_by_old_package_names.items(): + if len(new_names) > 1: + duplicate_new_names_strings.append("%s --> %s" % (old_name, new_names)) + if duplicate_new_names_strings: + raise ProjectConfigurationError( + "Multiple packages map in build config %s (sections %s) map to the " + "same export name %s" % (self.name, self.enabled_sections, + ", ".join(duplicate_new_names_strings))) + return dict([(old, new[0]) + for old, new in new_by_old_package_names.items()]) + + +class ProjectConfiguration(object): + """Reads a project configuration JSON file. + + Attributes: + _packages: All packages parsed from the configuration. + _json: JSON dictionary this configuration is read from. + _packages_by_name: Dictionary of enabled PackageConfiguration instances + indexed by name. + _selected_sections: Export sections used to enable this project. + _version: Version of the project or None if no version was specified on + initialization. + _all_builds: All available build configuration instances. + _builds: Set of builds filtered by enabled export sections. + """ + + def __init__(self, export_configuration_dict, selected_sections, version): + """Parse an project configuration string. + + Args: + export_configuration_dict: Dictionary with the configuration to parse. + selected_sections: Set of enabled export section strings. This is used to + filter the loaded package configurations. + See PackageConfiguration.get_enabled(). + version: Version number of this project. Can be None if this is + not versioned. + + Raises: + ProjectConfigurationError: If any project data contains errors. + """ + self._json = export_configuration_dict + self._packages = [PackageConfiguration(self, package_json) + for package_json in safe_dict_get_value( + self._json, "packages", default_value=[])] + self._packages_by_name = collections.OrderedDict() + self._version = version + if FLAGS.enforce_semver: + if version is None: + raise ProjectConfigurationError( + "Version number is required to export to Unity Package Manager " + + "packages.") + elif not VALID_VERSION_RE.match(version): + raise ProjectConfigurationError( + "Invalid version '%s'. Should be 'major.minor.patch(-preview)' in " + "numbers" % version) + + self._all_builds = [BuildConfiguration(build_json) + for build_json in safe_dict_get_value( + self._json, "builds", default_value=[{}])] + self._builds = [] + self._selected_sections = None + + # pylint: disable=g-missing-from-attributes + self.selected_sections = selected_sections + + @property + def packages_by_name(self): + """Get the list of packages from the configuration indexed by name. + + Returns: + Dictionary of PackageConfiguration instances indexed by name. + """ + return collections.OrderedDict(self._packages_by_name) + + @property + def packages(self): + """Get the list of packages from the configuration. + + Returns: + List of PackageConfiguration instances. + """ + return list(self._packages_by_name.values()) + + @property + def version(self): + """Get the version of this project. + + Returns: + Version string or None if no version was specified on initialization. + """ + return self._version + + @property + def selected_sections(self): + """Get the sections used to initialize this instance. + + Returns: + Set of currently selected section strings enabled for this project. + """ + return set(self._selected_sections) + + @selected_sections.setter + def selected_sections(self, sections): + """Filter the set of packages and build configurations by export sections. + + This method changes the set of selected packages returned by the + packages and packages_by_name properties and build configurations by the + builds property. + + Args: + sections: Set of enabled export section strings. This is used to + filter the loaded package and build configurations. + + Raises: + ProjectConfigurationError: If any project data contains errors. + """ + # If the set of selected sections hasn't changed, do nothing. + sections = set(sections) + if sections == self._selected_sections: + return + + # Filter by enabled packages and bucket by name. + package_list_by_name = collections.OrderedDict() + for package in self._packages: + if package.get_enabled(sections): + packages_list = package_list_by_name.get(package.name, []) + packages_list.append(package) + package_list_by_name[package.name] = packages_list + else: + logging.debug("Package %s not enabled for sections %s (supports %s)", + package.name, sections, package.sections) + + # Find any duplicate packages. + duplicate_package_names = ( + [name for name, pkgs in package_list_by_name.items() if len(pkgs) > 1]) + if duplicate_package_names: + raise ProjectConfigurationError( + ("Package(s) %s configured to export to the same path with " + "enabled sections %s") % (sorted(duplicate_package_names), + sorted(sections))) + + previous_selected_sections = self._selected_sections + previous_packages_by_name = self._packages_by_name + try: + self._selected_sections = sections + self._packages_by_name = collections.OrderedDict([ + (name, pkgs[0]) for name, pkgs in package_list_by_name.items()]) + + # Check for circular references in includes. + for package in self.packages: + package.check_circular_references_in_includes([]) + except ProjectConfigurationError as error: + self._selected_sections = previous_selected_sections + self._packages_by_name = previous_packages_by_name + raise error + + # Create a filtered set of build configuration instances. + self._builds = [] + for build in self._all_builds: + if build.get_enabled(sections): + self._builds.append(build) + else: + logging.debug("Build %s not enabled for sections %s (supports %s)", + build.name, sections, build.sections) + + @property + def builds(self): + """Get selected build configurations for this project. + + Returns: + List of BuildConfiguration instances associated with this project. + """ + return list(self._builds) + + def write(self, + guid_database, + assets_dirs, + output_dir, + timestamp, + for_upm=False): + """Export all enabled packages using the project build configs. + + Args: + guid_database: GuidDatabase instance which contains GUIDs for each + exported asset. + assets_dirs: List of paths to directories containing assets to import. + This is combined with the path of each asset referenced by the project. + output_dir: Directory where to write the exported .unitypackage. + timestamp: Timestamp to apply to all packaged assets in each archive. If + this value is less than 0, the creation time of each input file is used + instead. + for_upm: Whether write for Unity Package Manager package. + + Returns: + Dictionary of BuildConfiguration instances indexed by the exported + package filename generated by the build config. + + Raises: + ProjectConfigurationError: If an error occurs while exporting the project. + MissingGuidsError: If any asset GUIDs are missing. + """ + selected_sections = self.selected_sections + build_by_package_filename = {} + + try: + build_sections_and_package_name_maps = [] + build_indices_by_package_filename = collections.defaultdict(list) + # Generate package name maps for each build configuration. + builds = self.builds + for build_index, build in enumerate(builds): + # Apply the build config. + build_sections = selected_sections.union(build.enabled_sections) + self.selected_sections = build_sections + + # Create the package filename mapping. + packages_by_name = self.packages_by_name + package_name_map = None + if for_upm: + package_name_to_output_filename = { + pkg.name: pkg.tarball_name + for pkg in packages_by_name.values() + if pkg.export_upm + } + else: + package_name_to_output_filename = { + pkg.name: pkg.name + for pkg in packages_by_name.values() + if pkg.export + } + package_name_map = build.create_package_name_map( + package_name_to_output_filename) + + # Store the build configuration. + build_sections_and_package_name_maps.append( + (build, build_sections, package_name_map)) + + # Map each filename to a set of build indices so it's possible to + # check for duplicates later. + for filename in package_name_map.values(): + build_indices_by_package_filename[filename].append(build_index) + + # Check for multiple build configurations exporting to the same filenames. + duplicate_filename_errors = [] + for filename, build_indices in build_indices_by_package_filename.items(): + if len(build_indices) > 1: + duplicate_filename_errors.append( + "%s exported by multiple builds %s" % ( + filename, + str([builds[index].name for index in build_indices]))) + if duplicate_filename_errors: + raise ProjectConfigurationError( + ("Detected multiple builds exporting packages to the same " + "file(s).\n" + "%s") % "\n".join(duplicate_filename_errors)) + + missing_guid_paths = [] + for build, build_sections, package_name_map in ( + build_sections_and_package_name_maps): + # Apply the build config. + logging.info("Building %s using sections %s", build.name, + str(build_sections)) + self.selected_sections = build_sections + packages_by_name = self.packages_by_name + + # Export all packages. + for package_name, package_filename in package_name_map.items(): + package = packages_by_name[package_name] + try: + if for_upm: + filename = package.write_upm( + guid_database, + assets_dirs, + output_dir, + timestamp, + package_filename=package_filename) + else: + filename = package.write( + guid_database, + assets_dirs, + output_dir, + timestamp, + package_filename=package_filename) + build_by_package_filename[filename] = build + except MissingGuidsError as missing_guids_error: + logging.error("Missing GUIDs while writing %s (%s)", + package.name, missing_guids_error.missing_guid_paths) + missing_guid_paths.extend(missing_guids_error.missing_guid_paths) + except DuplicateGuidsError as error: + raise ProjectConfigurationError( + "Duplicate GUIDs detecting while writing package %s to %s " + "(%s)" % (package.name, package_filename, str(error))) + + if missing_guid_paths: + raise MissingGuidsError(missing_guid_paths) + finally: + self.selected_sections = selected_sections + return build_by_package_filename + + +def read_json_file_into_ordered_dict(json_filename): + """Load JSON into an OrderedDict. + + By default the JSON parser reads data into a tree of Python dictionaries that + do not preserve order. This method reads JSON into a tree of OrderedDict + instances that preserves order and provides some compatibility with YAML. + + Args: + json_filename: File to read JSON from. + + Returns: + OrderedDict read from json_filename. + + Raises: + IOError: If the file can't be read. + ValueError: If there is a parse error while reading the file. + """ + json_dict = None + with open(json_filename, "rt", encoding='utf-8') as json_file: + try: + json_dict = json.loads(json_file.read(), + object_pairs_hook=collections.OrderedDict) + except ValueError as error: + raise ValueError("Failed to load JSON file %s (%s)" % (json_filename, + str(error))) + return json_dict + + +def create_directory_from_zip(zip_filename): + """Optionally creates a directory from a zip file. + + Args: + zip_filename: Zip file to extract. + + Returns: + Temporary directory containing the contents of the zip file. + + Raises: + IOError: If an error occurs while extracting the zip file. + """ + temporary_dir = tempfile.mkdtemp() + try: + logging.debug("Unpacking zip file %s to %s...", zip_filename, temporary_dir) + with zipfile.ZipFile(zip_filename, "r") as zip_file: + zip_file.extractall(temporary_dir) + logging.debug("Unpacked zip file %s to %s", zip_filename, temporary_dir) + except IOError as error: + shutil.rmtree(temporary_dir) + raise error + return temporary_dir + + +def write_zipfile(zip_filename, source_directory): + """Write the contents of a directory to a zip file. + + Args: + zip_filename: Zip filename to create. + source_directory: Directory to archive. + + Raises: + IOError: If an error occurs while archiving. + """ + if os.path.exists(zip_filename): + os.unlink(zip_filename) + + logging.debug("Archiving directory %s to %s...", source_directory, + zip_filename) + with zipfile.ZipFile(zip_filename, "w", allowZip64=True) as zip_file: + for current_root, _, filenames in os.walk(source_directory): + for filename in filenames: + fullpath = os.path.join(current_root, filename) + zip_file.write(fullpath, os.path.relpath(fullpath, source_directory)) + logging.debug("Archived directory %s to %s", source_directory, zip_filename) + + +def copy_files_to_dir(colon_separated_input_output_filenames, + output_dir): + """Copy any additional files to the output directory. + + Args: + colon_separated_input_output_filenames: List of colon separated strings + that specifies "input_filename:output_filename" paths. + input_filename is the path to copy. output_filename can be optionally + specified to copy input_filename into a different path under output_dir. + If output_filename isn't specified, this method uses the input_filename. + output_dir: Directory to copy the files into. + + Returns: + List of files copied to the output directory. + """ + copied_files = [] + for additional_file in colon_separated_input_output_filenames: + additional_file_args = additional_file.split(":") + input_filename = posix_path(additional_file_args[0]) + + # Get the output filename. + output_filename = input_filename + if len(additional_file_args) > 1: + output_filename = additional_file_args[1] + output_filename = os.path.splitdrive(output_filename)[1] + + # Remove the drive or root directory from the output filename. + if os.path.normpath(output_filename).startswith(os.path.sep): + output_filename = output_filename[len(os.path.sep):] + output_filename = posix_path(os.path.join(output_dir, output_filename)) + + # Copy the file to the output directory. + copy_and_set_rwx(input_filename, output_filename) + copied_files.append(posix_path(output_filename)) + return copied_files + + +def find_in_dirs(filename, directories): + """Find the file under given directories. + + Args: + filename: File/Folder name to find. + directories: List of directories to search for. + + Returns: + If found, return the combined path with directory and filename. + Return None, otherwise. + """ + for directory in directories: + candidate = os.path.join(directory, filename) + if os.path.exists(candidate): + return candidate + return None + + +def main(unused_argv): + """Builds Unity packages from sets of files within an assets folder. + + Args: + unused_argv: List of arguments passed on the command line after the command. + + Returns: + The exit code status; 1 for error, 0 for success. + """ + if not FLAGS.config_file: + print >> sys.stderr, main.__doc__ + return 1 + + enabled_sections = set(FLAGS.enabled_sections or []) + assets_dirs = list(FLAGS.assets_dir or []) + temporary_assets_dirs = [] + output_dir = FLAGS.output_dir + + try: + if FLAGS.output_zip: + output_dir = tempfile.mkdtemp() + elif not os.path.exists(output_dir): + try: + os.makedirs(output_dir) + except FileExistsError: + # This can be racy with other build scripts. + pass + elif not os.path.isdir(output_dir): + logging.error("output_dir %s is not a directory", output_dir) + return 1 + + if FLAGS.output_upm and not FLAGS.enforce_semver: + logging.error("enforce_semver flag should be True when output_upm flag " + "is set to True") + return 1 + + if FLAGS.assets_zip: + try: + for asset_zip_file in FLAGS.assets_zip: + temporary_assets_dirs.append( + create_directory_from_zip(asset_zip_file)) + except IOError as error: + logging.error("Failed to extract assets zip file %s (%s)", + FLAGS.assets_zip, str(error)) + return 1 + + if FLAGS.asset_file: + asset_files_dir = tempfile.mkdtemp() + temporary_assets_dirs.append(asset_files_dir) + try: + copy_files_to_dir(FLAGS.asset_file, asset_files_dir) + except IOError as error: + logging.error("Failed while copying input files (%s)", str(error)) + return 1 + + duplicate_guids_checker = DuplicateGuidsChecker() + guids_json = {} + if FLAGS.guids_file: + try: + guids_json = read_json_file_into_ordered_dict(FLAGS.guids_file) + except (IOError, ValueError) as error: + logging.error("Failed to load GUIDs JSON from %s (%s)", + FLAGS.guids_file, str(error)) + return 1 + guid_database = GuidDatabase(duplicate_guids_checker, guids_json, + FLAGS.plugins_version) + try: + duplicate_guids_checker.check_for_duplicates() + except DuplicateGuidsError as duplicate_guids_error: + logging.error(str(duplicate_guids_error)) + return 1 + + try: + project = ProjectConfiguration( + read_json_file_into_ordered_dict(FLAGS.config_file), enabled_sections, + FLAGS.plugins_version) + except (IOError, ValueError, ProjectConfigurationError) as error: + logging.error("Error while parsing project configuration from %s (%s)", + FLAGS.config_file, str(error)) + return 1 + + assets_dirs.extend(temporary_assets_dirs) + + if FLAGS.output_unitypackage: + try: + project.write( + guid_database, + assets_dirs, + output_dir, + FLAGS.timestamp, + for_upm=False) + except ProjectConfigurationError as error: + logging.error(str(error)) + return 1 + + # Copy any additional files to the output directory. + try: + copy_files_to_dir(FLAGS.additional_file or [], output_dir) + except IOError as error: + logging.error("Failed while copying additional output files (%s)", + str(error)) + return 1 + + # Generate tgz packages for Unity Package Manager + if FLAGS.output_upm: + try: + project.write( + guid_database, + assets_dirs, + output_dir, + FLAGS.timestamp, + for_upm=True) + except ProjectConfigurationError as error: + logging.error(str(error)) + return 1 + + # Generate the output zip file if one is requested. + if FLAGS.output_zip: + try: + write_zipfile(FLAGS.output_zip, output_dir) + except IOError as error: + logging.error("Failed when writing output zip file %s (%s)", + FLAGS.output_zip, str(error)) + return 1 + + finally: + for temporary_dir in temporary_assets_dirs: + shutil.rmtree(temporary_dir) + if output_dir != FLAGS.output_dir: + shutil.rmtree(output_dir) + + return 0 + +if __name__ == "__main__": + flags.mark_flag_as_required("config_file") + app.run(main) diff --git a/source/ExportUnityPackage/export_unity_package_test.py b/source/ExportUnityPackage/export_unity_package_test.py new file mode 100755 index 00000000..8e9eeb5e --- /dev/null +++ b/source/ExportUnityPackage/export_unity_package_test.py @@ -0,0 +1,3472 @@ +#!/usr/bin/python +# Copyright 2018 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for Unity packager export_unity_package.py.""" + +import collections +import copy +import filecmp +import json +import os +import platform +import re +import shutil +import stat +import sys +import tarfile +import time +from absl import flags +from absl.testing import absltest + +# pylint: disable=C6204 +# pylint: disable=W0403 +sys.path.append(os.path.dirname(__file__)) +import export_unity_package +# pylint: enable=C6204 +# pylint: enable=W0403 + +FLAGS = flags.FLAGS + +# Location of test data. +TEST_DATA_PATH = os.path.join(os.path.dirname(__file__), "test_data") + +try: + unicode("") # See whether unicode class is available (Python < 3) +except NameError: + unicode = str # pylint: disable=redefined-builtin,invalid-name + + +class DuplicateGuidsCheckerTest(absltest.TestCase): + """Test the DuplicateGuidsChecker class.""" + + def test_add_guid_and_path(self): + checker = export_unity_package.DuplicateGuidsChecker() + checker.add_guid_and_path("816270c2a2a348e59cb9b7b096a24f50", + "Firebase/Plugins/Firebase.Analytics.dll") + checker.add_guid_and_path("7311924048bd457bac6d713576c952da", + "Firebase/Plugins/Firebase.App.dll") + checker.add_guid_and_path("7311924048bd457bac6d713576c952da", + "Firebase/Plugins/Firebase.Auth.dll") + self.assertEqual( + set(["Firebase/Plugins/Firebase.Analytics.dll"]), + checker._paths_by_guid.get("816270c2a2a348e59cb9b7b096a24f50")) + self.assertEqual( + set(["Firebase/Plugins/Firebase.App.dll", + "Firebase/Plugins/Firebase.Auth.dll"]), + checker._paths_by_guid.get("7311924048bd457bac6d713576c952da")) + + def test_check_for_duplicate_guids(self): + """Ensure an exception is raised if multiple files are found per GUID.""" + checker = export_unity_package.DuplicateGuidsChecker() + checker.add_guid_and_path("816270c2a2a348e59cb9b7b096a24f50", + "Firebase/Plugins/Firebase.Analytics.dll") + checker.add_guid_and_path("7311924048bd457bac6d713576c952da", + "Firebase/Plugins/Firebase.App.dll") + checker.add_guid_and_path("7311924048bd457bac6d713576c952da", + "Firebase/Plugins/Firebase.Auth.dll") + with self.assertRaises(export_unity_package.DuplicateGuidsError) as ( + context): + checker.check_for_duplicates() + self.assertEqual( + {"7311924048bd457bac6d713576c952da": set([ + "Firebase/Plugins/Firebase.App.dll", + "Firebase/Plugins/Firebase.Auth.dll"])}, + context.exception.paths_by_guid) + + def test_check_for_duplicate_guids_no_duplicates(self): + """Ensure an exception is not raised if there are no duplicate GUIDs.""" + checker = export_unity_package.DuplicateGuidsChecker() + checker.add_guid_and_path("816270c2a2a348e59cb9b7b096a24f50", + "Firebase/Plugins/Firebase.Analytics.dll") + checker.add_guid_and_path("7311924048bd457bac6d713576c952da", + "Firebase/Plugins/Firebase.App.dll") + checker.add_guid_and_path("275bd6b96a28470986154b9a995e191c", + "Firebase/Plugins/Firebase.Auth.dll") + checker.check_for_duplicates() + + +def delete_temporary_directory_contents(): + """Delete the contents of the temporary directory.""" + # If the temporary directory is populated, delete everything in there. + directory = FLAGS.test_tmpdir + for path in os.listdir(directory): + full_path = os.path.join(directory, path) + if os.path.isdir(full_path): + shutil.rmtree(full_path) + else: + os.unlink(full_path) + + +class SafeDictGetValueTest(absltest.TestCase): + """Test reading from a dictionary with error checking.""" + + def test_safe_dict_get_value_empty(self): + """Get a value from an empty dictionary.""" + self.assertEqual(None, export_unity_package.safe_dict_get_value({}, "test")) + + def test_safe_dict_get_value_empty_with_default(self): + """Get a value from an empty dictionary with a default value.""" + self.assertEqual("hello", export_unity_package.safe_dict_get_value( + {}, "test", default_value="hello")) + + def test_safe_dict_get_value_any_class(self): + """Get a value from a dictionary of any class.""" + self.assertEqual("hello", export_unity_package.safe_dict_get_value( + {"test": "hello"}, "test")) + self.assertEqual(["hello"], export_unity_package.safe_dict_get_value( + {"test": ["hello"]}, "test")) + + def test_safe_dict_get_value_matching_class(self): + """Get a value from a dictionary with a matching class.""" + self.assertEqual("hello", export_unity_package.safe_dict_get_value( + {"test": "hello"}, "test", value_classes=[str])) + self.assertEqual(u"hello", export_unity_package.safe_dict_get_value( + {"test": u"hello"}, "test", value_classes=[str, unicode])) + self.assertEqual(["hello"], export_unity_package.safe_dict_get_value( + {"test": ["hello"]}, "test", value_classes=[list])) + + def test_safe_dict_get_value_matching_class_from_default(self): + """Get a value from a dictionary with a matching class.""" + self.assertEqual("hello", export_unity_package.safe_dict_get_value( + {"test": "hello"}, "test", default_value="goodbye")) + self.assertEqual(u"hello", export_unity_package.safe_dict_get_value( + {"test": u"hello"}, "test", default_value="goodbye")) + self.assertEqual(["hello"], export_unity_package.safe_dict_get_value( + {"test": ["hello"]}, "test", default_value=["goodbye"])) + + def test_safe_dict_get_value_mismatching_class(self): + """Get a value from a dictionary with a mismatching class.""" + self.assertEqual(None, export_unity_package.safe_dict_get_value( + {"bish": ["hello"]}, "bish", value_classes=[str])) + self.assertEqual(None, export_unity_package.safe_dict_get_value( + {"test": "hello"}, "test", value_classes=[list])) + + def test_safe_dict_get_value_mismatching_class_from_default(self): + """Get a value from a dictionary with a mismatching class.""" + self.assertEqual("goodbye", export_unity_package.safe_dict_get_value( + {"test": ["hello"]}, "test", default_value="goodbye")) + self.assertEqual(["goodbye"], export_unity_package.safe_dict_get_value( + {"test": "hello"}, "test", default_value=["goodbye"])) + + +class SafeDictSetValueTest(absltest.TestCase): + """Test write to a dictionary with error checking.""" + + def test_safe_dict_set_value_non_dict_type(self): + """Set a value to a non-dict type.""" + # Set a value to None + self.assertEqual( + None, export_unity_package.safe_dict_set_value(None, "test", None)) + self.assertEqual( + None, export_unity_package.safe_dict_set_value(None, "test", "value")) + + # Set a value to a list + self.assertEqual([], + export_unity_package.safe_dict_set_value([], "test", None)) + self.assertEqual([], + export_unity_package.safe_dict_set_value([], "test", + "value")) + + # Set a value to an integer + self.assertEqual(1, + export_unity_package.safe_dict_set_value(1, "test", None)) + self.assertEqual( + 1, export_unity_package.safe_dict_set_value(1, "test", "value")) + + # Set a value to a string + self.assertEqual( + "node", export_unity_package.safe_dict_set_value("node", "test", None)) + self.assertEqual( + "node", + export_unity_package.safe_dict_set_value("node", "test", "value")) + + # Set a value to a set + self.assertEqual({"item1"}, + export_unity_package.safe_dict_set_value({"item1"}, "test", + None)) + self.assertEqual({"item1"}, + export_unity_package.safe_dict_set_value({"item1"}, "test", + "value")) + + def test_safe_dict_set_value_to_dict(self): + """Set a value to a dict.""" + empty_dict = {} + empty_dict_return = export_unity_package.safe_dict_set_value( + empty_dict, "test", "value") + empty_dict_expected = {"test": "value"} + self.assertEqual(empty_dict_expected, empty_dict) + self.assertEqual(empty_dict_expected, empty_dict_return) + + dict_with_other_key = {"some_key": "some_value"} + dict_with_other_key_return = export_unity_package.safe_dict_set_value( + dict_with_other_key, "test", "value") + dict_with_other_key_expected = {"some_key": "some_value", "test": "value"} + self.assertEqual(dict_with_other_key_expected, dict_with_other_key) + self.assertEqual(dict_with_other_key_expected, dict_with_other_key_return) + + dict_with_existing_key = {"test": "other_value"} + dict_with_existing_key_return = export_unity_package.safe_dict_set_value( + dict_with_existing_key, "test", "value") + dict_with_existing_key_expected = {"test": "value"} + self.assertEqual(dict_with_existing_key_expected, dict_with_existing_key) + self.assertEqual(dict_with_existing_key_expected, + dict_with_existing_key_return) + + def test_safe_dict_set_value_none_to_dict(self): + """Set None to a dict.""" + empty_dict = {} + empty_dict_return = export_unity_package.safe_dict_set_value( + empty_dict, "test", None) + self.assertEqual({}, empty_dict) + self.assertEqual({}, empty_dict_return) + + dict_with_other_key = {"some_key": "some_value"} + dict_with_other_key_return = export_unity_package.safe_dict_set_value( + dict_with_other_key, "test", None) + self.assertEqual({"some_key": "some_value"}, dict_with_other_key) + self.assertEqual({"some_key": "some_value"}, dict_with_other_key_return) + + dict_with_existing_key = {"test": "some_value"} + dict_with_existing_key_return = export_unity_package.safe_dict_set_value( + dict_with_existing_key, "test", None) + self.assertEqual({}, dict_with_existing_key) + self.assertEqual({}, dict_with_existing_key_return) + + +class GuidDatabaseTest(absltest.TestCase): + """Test reading GUIDs from .meta files and the GUID cache.""" + + def test_init_and_query_guids(self): + """Read GUIDs from JSON string.""" + database = export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), + { + "1.0.0": { + "A/B.cs": "ba9f9118207d46248936105077947525", + "C/D.dll": "84bde502cd4a4a98add4c90441d7e158" + }, + "1.2.3": { + "A/B.cs": "df2d7d4d6f6345609df6159fe468b61f" + } + }, + "1.2.3") + + self.assertEqual("df2d7d4d6f6345609df6159fe468b61f", + database.get_guid("A/B.cs")) + self.assertEqual("84bde502cd4a4a98add4c90441d7e158", + database.get_guid("C/D.dll")) + with self.assertRaises(export_unity_package.MissingGuidsError) as context: + unused_guid = database.get_guid("E/F.png") + self.assertEqual(["E/F.png"], context.exception.missing_guid_paths) + + def test_init_duplicate_guids(self): + """Initialize the GUID database with duplicate GUIDs.""" + duplicate_guids_checker = export_unity_package.DuplicateGuidsChecker() + unused_database = export_unity_package.GuidDatabase( + duplicate_guids_checker, + { + "1.0.0": { + "A/B.cs": "ba9f9118207d46248936105077947525", + "C/D.cs": "ba9f9118207d46248936105077947525" + } + }, + "1.0.0") + with self.assertRaises(export_unity_package.DuplicateGuidsError) as context: + duplicate_guids_checker.check_for_duplicates() + self.assertEqual({"ba9f9118207d46248936105077947525": set(["A/B.cs", + "C/D.cs"])}, + context.exception.paths_by_guid) + + def test_read_guids_from_assets(self): + """Read GUIDs from a tree of Unity assets.""" + database = export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), "", "1.0.0") + + with self.assertRaises(export_unity_package.MissingGuidsError) as context: + database.read_guids_from_assets([ + export_unity_package.Asset( + "PlayServicesResolver/Editor/Google.VersionHandler.dll", None, + collections.OrderedDict( + [("guid", "06f6f385a4ad409884857500a3c04441")])), + export_unity_package.Asset( + "Firebase/Plugins/Firebase.Analytics.dll", None, + collections.OrderedDict()), + export_unity_package.Asset( + "Firebase/Plugins/Firebase.App.dll", None, + collections.OrderedDict())]) + self.assertEqual(["Firebase/Plugins/Firebase.Analytics.dll", + "Firebase/Plugins/Firebase.App.dll"], + context.exception.missing_guid_paths) + self.assertEqual( + "06f6f385a4ad409884857500a3c04441", + database.get_guid( + "PlayServicesResolver/Editor/Google.VersionHandler.dll")) + + +class YamlSerializerTest(absltest.TestCase): + """Test reading / writing YAML.""" + + def test_read_yaml(self): + """Read YAML into an ordered dictionary.""" + serializer = export_unity_package.YamlSerializer() + yaml_dict = serializer.load("Object: [1, 2, 3]\n" + "AnotherObject:\n" + " someData: foo\n" + " moreData: bar\n" + " otherData:\n") + self.assertEqual([(0, "Object"), + (1, "AnotherObject")], + list(enumerate(yaml_dict))) + self.assertEqual([1, 2, 3], yaml_dict["Object"]) + self.assertEqual([(0, "someData"), (1, "moreData"), (2, "otherData")], + list(enumerate(yaml_dict["AnotherObject"]))) + self.assertEqual("foo", yaml_dict["AnotherObject"]["someData"]) + self.assertEqual("bar", yaml_dict["AnotherObject"]["moreData"]) + self.assertEqual(None, yaml_dict["AnotherObject"]["otherData"]) + + def test_write_yaml(self): + """Write YAML from an ordered dictionary.""" + serializer = export_unity_package.YamlSerializer() + object_list = [1, 2, 3] + yaml_string = serializer.dump(collections.OrderedDict( + [("Object", object_list), + ("AnotherObject", + collections.OrderedDict( + # Also, ensure unicode strings are serialized as plain strings. + [("someData", u"foo"), + ("moreData", "bar"), + ("otherData", None)])), + ("Object2", object_list)])) + self.assertEqual("Object:\n" + "- 1\n" + "- 2\n" + "- 3\n" + "AnotherObject:\n" + " someData: foo\n" + " moreData: bar\n" + " otherData:\n" + "Object2:\n" + "- 1\n" + "- 2\n" + "- 3\n", + yaml_string) + + +class MergeOrderedDictsTest(absltest.TestCase): + """Test merging ordered dictionaries.""" + + def test_merge_with_empty(self): + """"Merge a dictionary with an empty dictionary.""" + merge_into = collections.OrderedDict() + merge_from = collections.OrderedDict( + [("a", collections.OrderedDict( + [("b", [1, 2, 3]), + ("c", "hello")])), + ("d", "bye")]) + self.assertEqual(merge_into, + export_unity_package.merge_ordered_dicts( + merge_into, + copy.deepcopy(merge_from))) + self.assertEqual(merge_from, merge_into) + + def test_merge_from_empty(self): + """"Merge an empty dictionary with a dictionary.""" + expected = collections.OrderedDict( + [("a", collections.OrderedDict( + [("b", [1, 2, 3]), + ("c", "hello")])), + ("d", "bye")]) + merge_into = copy.deepcopy(expected) + merge_from = collections.OrderedDict() + self.assertEqual(merge_into, + export_unity_package.merge_ordered_dicts(merge_into, + merge_from)) + self.assertEqual(expected, merge_into) + + def test_merge_non_dictionaries(self): + """Try merging non-dictionary objects.""" + self.assertEqual(["do not merge"], + export_unity_package.merge_ordered_dicts(["do not merge"], + {"something"})) + self.assertEqual({"something"}, + export_unity_package.merge_ordered_dicts({"something"}, + ["do not merge"])) + + def test_merge_nested(self): + """Merge nested items in a dictionary with another dictionary.""" + merge_into = collections.OrderedDict( + [("a", collections.OrderedDict( + [("b", [1, 2, 3]), + ("c", collections.OrderedDict( + [("hello", "goodbye"), + ("bonjour", "au revior")]))])), + ("d", "foo")]) + merge_from = collections.OrderedDict( + [("a", collections.OrderedDict( + [("b", [4, 5, 6]), + ("c", collections.OrderedDict( + [("bonjour", "is french")]))]))]) + expected = collections.OrderedDict( + [("a", collections.OrderedDict( + [("b", [4, 5, 6]), + ("c", collections.OrderedDict( + [("hello", "goodbye"), + ("bonjour", "is french")]))])), + ("d", "foo")]) + self.assertEqual(merge_into, + export_unity_package.merge_ordered_dicts( + merge_into, merge_from)) + self.assertEqual(expected, merge_into) + + def test_merge_first_second(self): + """Merge nested items in a list of dictionaries.""" + merge_into = collections.OrderedDict( + [("PluginImporter", collections.OrderedDict( + [("platformData", [ + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Any", None)])), + ("second", collections.OrderedDict( + [("enabled", 1), + ("settings", collections.OrderedDict( + [("CPU", "AnyCPU")]))]))]), + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Standalone", "Linux")])), + ("second", collections.OrderedDict( + [("enabled", 1), + ("settings", collections.OrderedDict( + [("CPU", "x86")]))]))]), + ])])) + ]) + merge_from = collections.OrderedDict( + [("PluginImporter", collections.OrderedDict( + [("platformData", [ + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Any", None)])), + ("second", collections.OrderedDict( + [("enabled", 0), + ("settings", collections.OrderedDict( + [("CPU", "AnyCPU")]))]))]), + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Standalone", "Windows")])), + ("second", collections.OrderedDict( + [("enabled", 1), + ("settings", collections.OrderedDict( + [("CPU", "x86_64")]))]))]), + ])])) + ]) + expected = collections.OrderedDict( + [("PluginImporter", collections.OrderedDict( + [("platformData", [ + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Any", None)])), + ("second", collections.OrderedDict( + [("enabled", 0), + ("settings", collections.OrderedDict( + [("CPU", "AnyCPU")]))]))]), + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Standalone", "Linux")])), + ("second", collections.OrderedDict( + [("enabled", 1), + ("settings", collections.OrderedDict( + [("CPU", "x86")]))]))]), + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Standalone", "Windows")])), + ("second", collections.OrderedDict( + [("enabled", 1), + ("settings", collections.OrderedDict( + [("CPU", "x86_64")]))]))]), + ])])) + ]) + self.assertEqual(merge_into, + export_unity_package.merge_ordered_dicts( + merge_into, merge_from)) + self.assertEqual(expected, merge_into) + + +class ConfigurationBlockTest(absltest.TestCase): + """Test parsing common configuration options from JSON.""" + + def test_block_with_sections(self): + """Test parsing a block with conditionally enabled sections.""" + block = export_unity_package.ConfigurationBlock(json.loads( + """ + { + "sections": ["foo", "bar"] + } + """)) + self.assertEqual(set(["bar", "foo"]), block.sections) + self.assertTrue(block.get_enabled(set(["foo"]))) + self.assertTrue(block.get_enabled(set(["bar"]))) + self.assertTrue(block.get_enabled(set(["bar", "baz"]))) + self.assertFalse(block.get_enabled(set(["baz"]))) + self.assertFalse(block.get_enabled(set())) + + def test_block_with_no_sections(self): + """Test parsing a block with no conditionally enabled sections.""" + block = export_unity_package.ConfigurationBlock(json.loads("{}")) + self.assertEqual(set(), block.sections) + self.assertTrue(block.get_enabled(set(["foo", "bar"]))) + self.assertTrue(block.get_enabled(set())) + + +class ProjectConfigurationTest(absltest.TestCase): + """Test parsing the project configuration from JSON.""" + + def setUp(self): + """Setup a common configuration for a subset of tests.""" + super(ProjectConfigurationTest, self).setUp() + self.old_flag = FLAGS.enforce_semver + # Turn off the enforce for most of the test + FLAGS.enforce_semver = False + + def tearDown(self): + """Clean up the temporary directory.""" + super(ProjectConfigurationTest, self).tearDown() + FLAGS.enforce_semver = self.old_flag + + def test_no_packages(self): + """Test parsing a project with no packages.""" + config = export_unity_package.ProjectConfiguration({"packages": []}, + set(), None) + self.assertEqual([], list(config.packages)) + self.assertEqual({}, config.packages_by_name) + self.assertEqual(set(), config.selected_sections) + self.assertEqual([""], [build.name for build in config.builds]) + + def test_no_packages_with_version(self): + """Test parsing a project with no packages with version.""" + config = export_unity_package.ProjectConfiguration({"packages": []}, set(), + "1.22.333") + self.assertEqual([], list(config.packages)) + self.assertEqual({}, config.packages_by_name) + self.assertEqual(set(), config.selected_sections) + self.assertEqual([""], [build.name for build in config.builds]) + + def test_version_no_enforce(self): + """Test parsing a project version with no SemVer enforcement.""" + FLAGS.enforce_semver = False + self.assertEqual(None, + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), None).version) + + self.assertEqual("", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "").version) + + self.assertEqual("1", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "1").version) + + self.assertEqual("1.2", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "1.2").version) + + self.assertEqual("1a.2.3", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "1a.2.3").version) + + self.assertEqual(".2.3", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), ".2.3").version) + + def test_version_with_enforce(self): + """Test parsing a project version with SemVer enforcement.""" + FLAGS.enforce_semver = True + + self.assertEqual("0.0.0", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "0.0.0").version) + self.assertEqual("1.2.3", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "1.2.3").version) + self.assertEqual("111.22.3333", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "111.22.3333").version) + self.assertEqual("1.0.0-preview", + export_unity_package.ProjectConfiguration( + {"packages": []}, set(), "1.0.0-preview").version) + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.ProjectConfiguration({"packages": []}, set(), None) + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.ProjectConfiguration({"packages": []}, set(), "") + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.ProjectConfiguration({"packages": []}, set(), "1") + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.ProjectConfiguration({"packages": []}, set(), "1.2") + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.ProjectConfiguration({"packages": []}, set(), + "1a.2.3") + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.ProjectConfiguration({"packages": []}, set(), ".2.3") + + def test_enabled_package(self): + """Test parsing a project with an enabled package.""" + config = export_unity_package.ProjectConfiguration( + {"packages": [{"name": "FirebaseApp.unitypackage"}]}, set(), None) + self.assertEqual(["FirebaseApp.unitypackage"], + list(config.packages_by_name.keys())) + self.assertLen(config.packages, 1) + self.assertEqual("FirebaseApp.unitypackage", config.packages[0].name) + self.assertEqual(set(), config.selected_sections) + + def test_enable_package_and_builds_by_section(self): + """Test parsing a project with conditionally enabled packages.""" + config = export_unity_package.ProjectConfiguration( + { + "packages": [ + {"name": "FirebaseApp.unitypackage"}, + {"name": "FirebaseAppExperimental.unitypackage", + "sections": ["experimental"]}, + {"name": "FirebaseAppTest.unitypackage", + "sections": ["debug", "test"]} + ], + "builds": [ + {"name": "production"}, + {"name": "experimental", "sections": ["experimental"]}, + {"name": "debug", "sections": ["debug", "test"]}, + ], + }, set(), None) + self.assertCountEqual(["FirebaseApp.unitypackage"], + config.packages_by_name.keys()) + self.assertCountEqual(["production"], [b.name for b in config.builds]) + self.assertCountEqual(set(), config.selected_sections) + + config.selected_sections = set(["experimental"]) + self.assertCountEqual(["FirebaseApp.unitypackage", + "FirebaseAppExperimental.unitypackage"], + config.packages_by_name.keys()) + self.assertCountEqual(["experimental", "production"], + [b.name for b in config.builds]) + self.assertCountEqual(set(["experimental"]), config.selected_sections) + + config.selected_sections = set(["debug"]) + self.assertCountEqual(["FirebaseApp.unitypackage", + "FirebaseAppTest.unitypackage"], + config.packages_by_name.keys()) + self.assertCountEqual(["debug", "production"], + [b.name for b in config.builds]) + self.assertCountEqual(set(["debug"]), config.selected_sections) + + config.selected_sections = set(["test"]) + self.assertCountEqual(["FirebaseApp.unitypackage", + "FirebaseAppTest.unitypackage"], + config.packages_by_name.keys()) + self.assertCountEqual(["debug", "production"], + [b.name for b in config.builds]) + self.assertCountEqual(set(["test"]), config.selected_sections) + + config.selected_sections = set(["experimental", "debug"]) + self.assertCountEqual(["FirebaseApp.unitypackage", + "FirebaseAppExperimental.unitypackage", + "FirebaseAppTest.unitypackage"], + config.packages_by_name.keys()) + self.assertCountEqual(["debug", "experimental", "production"], + [b.name for b in config.builds]) + self.assertCountEqual(set(["debug", "experimental"]), + config.selected_sections) + + def test_duplicate_packages(self): + """Test parsing a project with duplicate packages.""" + config_json = { + "packages": [ + {"name": "FirebaseApp.unitypackage", "sections": ["public"]}, + {"name": "FirebaseApp.unitypackage", "sections": ["experimental"]}, + {"name": "FirebaseApp.unitypackage", "sections": ["debug", "test"]} + ] + } + + config = export_unity_package.ProjectConfiguration(config_json, + set(["public"]), None) + self.assertEqual(["FirebaseApp.unitypackage"], + list(config.packages_by_name.keys())) + + config = export_unity_package.ProjectConfiguration(config_json, + set(["experimental"]), + None) + self.assertEqual(["FirebaseApp.unitypackage"], + list(config.packages_by_name.keys())) + + # Enable conflicting packages for export. + with self.assertRaises(export_unity_package.ProjectConfigurationError) as ( + context): + config = export_unity_package.ProjectConfiguration( + config_json, set(["debug", "experimental"]), None) + self.assertRegexMatch( + str(context.exception), + [r"Package.*FirebaseApp\.unitypackage.*'debug', 'experimental'"]) + + def test_package_includes(self): + """Test parsing a project with packages that include each other.""" + config_json = { + "packages": [ + {"name": "FirebaseApp.unitypackage"}, + {"name": "Parse.unitypackage"}, + {"name": "FirebaseAnalytics.unitypackage", + "includes": ["FirebaseApp.unitypackage", "Parse.unitypackage"]}, + {"name": "PlayServicesResolver.unitypackage"} + ] + } + + config = export_unity_package.ProjectConfiguration(config_json, set(), None) + self.assertCountEqual( + ["FirebaseApp.unitypackage", "Parse.unitypackage"], + [include.name for include in ( + config.packages_by_name[ + "FirebaseAnalytics.unitypackage"].includes)]) + self.assertEqual([], config.packages_by_name[ + "PlayServicesResolver.unitypackage"].includes) + + def test_package_include_circular_reference(self): + """Test parsing a project with circular reference in includes.""" + config_json = { + "packages": [ + {"name": "FirebaseApp.unitypackage", + "includes": ["CommonUtilities.unitypackage"]}, + {"name": "Parse.unitypackage", + "includes": ["PlayServicesResolver.unitypackage"]}, + {"name": "CommonUtilities.unitypackage", + "includes": ["Parse.unitypackage", + "FirebaseAnalytics.unitypackage"]}, + {"name": "FirebaseAnalytics.unitypackage", + "includes": ["FirebaseApp.unitypackage", "Parse.unitypackage"]}, + {"name": "PlayServicesResolver.unitypackage"} + ] + } + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.ProjectConfiguration(config_json, set(), None) + + def test_package_include_missing(self): + """Test parsing a project with a missing reference in includes.""" + config_json = { + "packages": [ + {"name": "FirebaseApp.unitypackage", + "includes": ["CommonUtilities.unitypackage"]}, + {"name": "CommonUtilities.unitypackage", + "includes": ["Parse.unitypackage"]}, + {"name": "FirebaseAnalytics.unitypackage", + "includes": ["FirebaseApp.unitypackage"]} + ] + } + + with self.assertRaises(export_unity_package.ProjectConfigurationError) as ( + context): + export_unity_package.ProjectConfiguration(config_json, set(), None) + self.assertIn("Parse.unitypackage", str(context.exception)) + + def test_write_failure_due_to_duplicate_output_files(self): + """Test writing a project that maps multiple packages to the same files.""" + config = export_unity_package.ProjectConfiguration( + {"packages": [ + {"name": "FirebaseApp.unitypackage"}, + {"name": "FirebaseAnalytics.unitypackage", + "sections": ["experimental"]}, + {"name": "FirebaseAuth.unitypackage", + "sections": ["public"]}, + ], + "builds": [ + {"name": "public", "enabled_sections": ["public"]}, + {"name": "experimental", "enabled_sections": ["experimental"]}, + ]}, set(), None) + with self.assertRaises(export_unity_package.ProjectConfigurationError) as ( + context): + config.write( + export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), {}, "1.2.3"), + ["assets"], "output", 0) + self.assertRegexMatch( + str(context.exception), + ["FirebaseApp.unitypackage .* ['public', 'experimental']"]) + + def test_write_with_build_configs(self): + """Test writing the contents of a project with build configs.""" + + expected_guid_database = export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), {}, "1.2.3") + expected_assets_dirs = ["some/assets"] + expected_output_dir = "an/output/dir" + expected_timestamp = 123456789 + test_case_instance = self + + package_configuration_class = export_unity_package.PackageConfiguration + + class FakeWritePackageConfiguration( + export_unity_package.PackageConfiguration): + """PackageConfiguration with write()/write_upm() replaced with a fake.""" + + def __init__(self, project, package_json): + """Initialize this object with JSON package data. + + Args: + project: ProjectConfiguration instance this package was parsed from. + package_json: Package dictionary parsed from JSON project data. + + Raises: + ProjectConfigurationError: If the package has no name. + """ + # Restore the class before init so super() functions correctly. + export_unity_package.PackageConfiguration = package_configuration_class + super(FakeWritePackageConfiguration, self).__init__(project, + package_json) + export_unity_package.PackageConfiguration = ( + FakeWritePackageConfiguration) + + def write(self, guid_database, assets_dirs, output_dir, timestamp, + package_filename=None): + """Stubbed out implementation of write(). + + Args: + guid_database: Must equal expected_guid_database. + assets_dirs: Must equal expected_assets_dir. + output_dir: Must equal expected_output_dir. + timestamp: Must equal expected_timestamp. + package_filename: Returned by this method. + + Returns: + Value of package_filename. + """ + test_case_instance.assertEqual(expected_guid_database, guid_database) + test_case_instance.assertEqual(expected_assets_dirs, assets_dirs) + test_case_instance.assertEqual(expected_output_dir, output_dir) + test_case_instance.assertEqual(expected_timestamp, timestamp) + return package_filename + + def write_upm(self, + guid_database, + assets_dirs, + output_dir, + timestamp, + package_filename=None): + """Stubbed out implementation of write_upm(). + + Args: + guid_database: Must equal expected_guid_database. + assets_dirs: Must equal expected_assets_dir. + output_dir: Must equal expected_output_dir. + timestamp: Must equal expected_timestamp. + package_filename: Returned by this method. + + Returns: + Value of package_filename. + """ + test_case_instance.assertEqual(expected_guid_database, guid_database) + test_case_instance.assertEqual(expected_assets_dirs, assets_dirs) + test_case_instance.assertEqual(expected_output_dir, output_dir) + test_case_instance.assertEqual(expected_timestamp, timestamp) + return package_filename + + try: + export_unity_package.PackageConfiguration = FakeWritePackageConfiguration + config = export_unity_package.ProjectConfiguration( + { + "packages": [ + { + "name": "FirebaseApp.unitypackage", + "common_manifest": { + "name": "com.firebase.app" + }, + "export_upm": 1 + }, + { + "name": "FirebaseSpecialSauce.unitypackage", + "sections": ["experimental"], + "common_manifest": { + "name": "com.firebase.special_sauce" + }, + "export_upm": 1 + }, + ], + "builds": [ + { + "name": "public", + "enabled_sections": ["public"] + }, + { + "name": + "experimental", + "enabled_sections": ["experimental"], + "package_name_replacements": [ + { + "match": r"^(Firebase)([^.]+)(\.unitypackage)$", + "replacement": r"\1Secret\2Experiment\3" + }, + { + "match": r"^(com\.firebase\..+)(\.tgz)$", + "replacement": r"\1-preview\2" + }, + ] + }, + ] + }, set(), "1.2.3") + self.assertCountEqual( + [("FirebaseApp.unitypackage", "public"), + ("FirebaseSecretAppExperiment.unitypackage", "experimental"), + ("FirebaseSecretSpecialSauceExperiment.unitypackage", + "experimental")], + [(filename, build.name) for filename, build in config.write( + expected_guid_database, expected_assets_dirs, + expected_output_dir, expected_timestamp).items()]) + self.assertCountEqual( + [("com.firebase.app-1.2.3.tgz", "public"), + ("com.firebase.app-1.2.3-preview.tgz", "experimental"), + ("com.firebase.special_sauce-1.2.3-preview.tgz", "experimental")], + [(filename, build.name) for filename, build in config.write( + expected_guid_database, expected_assets_dirs, expected_output_dir, + expected_timestamp, for_upm=True).items()]) + finally: + export_unity_package.PackageConfiguration = package_configuration_class + + +class BuildConfigurationTest(absltest.TestCase): + """Test parsing a build configuration.""" + + def setUp(self): + """Setup a common configuration for a subset of tests.""" + super(BuildConfigurationTest, self).setUp() + self.config_json = { + "name": "Debug", + "sections": ["internal"], + "enabled_sections": ["debug", "test"], + "package_name_replacements": [ + {"match": "Firebase", + "replacement": "Fire"}, + {"match": r"([^.]+)\.unitypackage", + "replacement": r"\1Debug.unitypackage"}, + ], + } + + def test_create_empty(self): + """Create an empty build config.""" + config = export_unity_package.BuildConfiguration({}) + self.assertEqual(set(), config.sections) + self.assertEqual(set(), config.enabled_sections) + self.assertEqual("", config.name) + self.assertEqual([], config.package_name_replacements) + + def test_create(self): + """Create a named build config.""" + config = export_unity_package.BuildConfiguration(self.config_json) + self.assertEqual(set(["internal"]), config.sections) + self.assertEqual(set(["debug", "test"]), config.enabled_sections) + self.assertEqual("Debug", config.name) + self.assertEqual([("Firebase", "Fire"), + (r"([^.]+)\.unitypackage", + r"\1Debug.unitypackage")], + [(pattern_re.pattern, repl) + for pattern_re, repl in config.package_name_replacements]) + + def test_create_invalid_regex(self): + """Create a build config with an invalid regular expression.""" + with self.assertRaises(export_unity_package.ProjectConfigurationError) as ( + context): + export_unity_package.BuildConfiguration({ + "package_name_replacements": [{ + "match": r"(invalid", + "replacement": "ignored" + }] + }) + self.assertRegexMatch(str(context.exception), [r"\(invalid"]) + + def test_apply_package_name_replacements(self): + """Replace a package name with build config replacements.""" + config = export_unity_package.BuildConfiguration(self.config_json) + self.assertEqual("HotFireMagicExDebug.unitypackage", + config.apply_package_name_replacements( + "HotFirebaseMagicEx.unitypackage")) + + def test_apply_invalid_package_name_replacement(self): + """Try to apply an invalid replacement.""" + config = export_unity_package.BuildConfiguration({ + "package_name_replacements": [{ + "match": r"([^.]+)\.ext", + "replacement": r"\2something\1" + }] + }) + with self.assertRaises(export_unity_package.ProjectConfigurationError) as ( + context): + config.apply_package_name_replacements("test.ext") + self.assertRegexMatch(str(context.exception), [r"\\2something\\1"]) + + def test_create_package_name_map(self): + """Create a map of renamed package names.""" + config = export_unity_package.BuildConfiguration(self.config_json) + self.assertEqual( + { + "HotFirebaseMagicEx.unitypackage": + "HotFireMagicExDebug.unitypackage", + "FirebasePerf.unitypackage": + "FirePerformanceDebug.unitypackage" + }, + config.create_package_name_map({ + "HotFirebaseMagicEx.unitypackage": + "HotFirebaseMagicEx.unitypackage", + "FirebasePerf.unitypackage": + "FirebasePerformance.unitypackage" + })) + + +class PackageConfigurationTest(absltest.TestCase): + """Test parsing a package configuration.""" + + def setUp(self): + """Create an empty project config.""" + super(PackageConfigurationTest, self).setUp() + self.project = export_unity_package.ProjectConfiguration({}, set(), + "1.2.3") + # Metadata before write() is called. + self.expected_manifest_metadata_prebuild = copy.deepcopy( + export_unity_package.DEFAULT_METADATA_TEMPLATE) + self.expected_manifest_metadata_prebuild["labels"] = [ + "gvh", "gvh_manifest", "gvh_version-1.2.3", + "gvhp_manifestname-0NiceName", "gvhp_manifestname-1Test"] + # Metadata when write() is called. + self.expected_manifest_metadata = copy.deepcopy( + export_unity_package.DEFAULT_METADATA_TEMPLATE) + self.expected_manifest_metadata["labels"] = [ + "gvh", "gvh_manifest", "gvh_version-1.2.3", + "gvhp_exportpath-Foo/Bar/Test_version-1.2.3_manifest.txt", + "gvhp_manifestname-0Test"] + # Metadata before write_upm() is called. + self.expected_upm_manifest_metadata_prebuild = copy.deepcopy( + export_unity_package.DEFAULT_METADATA_TEMPLATE) + self.expected_upm_manifest_metadata_prebuild["labels"] = [ + "gupmr_manifest", "gvh", "gvh_version-1.2.3"] + # Metadata when write_upm() is called. + self.expected_upm_manifest_metadata = copy.deepcopy( + export_unity_package.DEFAULT_METADATA_TEMPLATE) + self.expected_upm_manifest_metadata["labels"] = [ + "gupmr_manifest", "gvh", "gvh_version-1.2.3", + "gvhp_exportpath-package.json"] + + def test_create_no_name(self): + """Create a package config with no name.""" + with self.assertRaises(export_unity_package.ProjectConfigurationError): + export_unity_package.PackageConfiguration(self.project, {}) + + def test_create_defaults_no_version(self): + """Create a package config with default values.""" + old_flag = FLAGS.enforce_semver + FLAGS.enforce_semver = False + config = export_unity_package.PackageConfiguration( + export_unity_package.ProjectConfiguration({}, set(), None), + {"name": "Test.unitypackage"}) + self.assertEqual("Test.unitypackage", config.name) + self.assertTrue(config.export) + self.assertEqual(None, config.manifest_path) + self.assertEqual(None, config.manifest_filename) + self.assertEqual( + None, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_LEGACY)) + self.assertEqual( + None, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_UPM)) + self.assertEqual([], config.imports) + self.assertEqual([], config.includes) + self.assertEqual([], config.exclude_paths) + self.assertEqual(set([]), config.labels) + self.assertEqual(None, config.version) + self.assertEqual("Test", config.package_name) + self.assertEqual("Test", config.common_package_display_name) + self.assertEqual(None, config.common_manifest) + self.assertEqual(None, config.common_package_name) + self.assertEqual(None, config.common_package_description) + self.assertFalse(config.export_upm) + self.assertEqual(None, config.tarball_name) + self.assertEqual(None, config.upm_package_config) + self.assertEqual(None, config.upm_manifest) + + FLAGS.enforce_semver = old_flag + + def test_create_defaults(self): + """Create a package config with default values.""" + config = export_unity_package.PackageConfiguration( + self.project, {"name": "Test.unitypackage"}) + self.assertEqual("Test.unitypackage", config.name) + self.assertTrue(config.export) + self.assertEqual(None, config.manifest_path) + self.assertEqual(None, config.manifest_filename) + self.assertEqual( + None, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_LEGACY)) + self.assertEqual( + self.expected_upm_manifest_metadata_prebuild, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_UPM)) + self.assertEqual([], config.imports) + self.assertEqual([], config.includes) + self.assertEqual([], config.exclude_paths) + self.assertEqual(set(["gvh", "gvh_version-1.2.3"]), config.labels) + self.assertEqual("1.2.3", config.version) + self.assertEqual("Test", config.package_name) + self.assertEqual("Test", config.common_package_display_name) + self.assertEqual(None, config.common_manifest) + self.assertEqual(None, config.common_package_name) + self.assertEqual(None, config.common_package_description) + self.assertFalse(config.export_upm) + self.assertEqual(None, config.tarball_name) + self.assertEqual(None, config.upm_package_config) + self.assertEqual(None, config.upm_manifest) + + def test_create_non_defaults(self): + """Create a package config with non-default values.""" + config = export_unity_package.PackageConfiguration( + self.project, { + "name": "Test.unitypackage", + "export": 0, + "manifest_path": "Foo/Bar", + "exclude_paths": ["a/b/c"], + "common_manifest": { + "name": "com.company.test", + "description": "Desc", + "display_name": "NiceName" + }, + "export_upm": 1, + "upm_package_config": { + "manifest": { + "unity": "2017.1" + } + } + }) + self.assertEqual("Test.unitypackage", config.name) + self.assertFalse(config.export) + self.assertEqual("Foo/Bar", config.manifest_path) + self.assertEqual("Foo/Bar/Test_version-1.2.3_manifest.txt", + config.manifest_filename) + self.assertEqual( + self.expected_manifest_metadata_prebuild, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_LEGACY)) + self.assertEqual( + self.expected_upm_manifest_metadata_prebuild, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_UPM)) + self.assertEqual([], config.imports) + self.assertEqual([], config.includes) + self.assertEqual([re.compile("a/b/c")], config.exclude_paths) + self.assertEqual(set(["gvh", "gvh_version-1.2.3"]), config.labels) + self.assertEqual("1.2.3", config.version) + self.assertEqual("Test", config.package_name) + self.assertEqual("NiceName", config.common_package_display_name) + self.assertEqual( + {"name": "com.company.test", "description": "Desc", + "display_name": "NiceName"}, + config.common_manifest) + self.assertEqual("com.company.test", config.common_package_name) + self.assertEqual("Desc", config.common_package_description) + self.assertTrue(config.export_upm) + self.assertEqual("com.company.test-1.2.3.tgz", config.tarball_name) + self.assertEqual({"manifest": { + "unity": "2017.1" + }}, config.upm_package_config) + self.assertEqual({"unity": "2017.1"}, config.upm_manifest) + + def test_create_non_defaults_desc_list(self): + """Create a package config with description as a list.""" + config = export_unity_package.PackageConfiguration( + self.project, { + "name": "Test.unitypackage", + "export": 0, + "manifest_path": "Foo/Bar", + "exclude_paths": ["a/b/c"], + "common_manifest": { + "name": "com.company.test", + "description": ["Desc", "123"], + "display_name": "NiceName" + }, + "export_upm": 1, + "upm_package_config": { + "manifest": { + "unity": "2017.1" + } + } + }) + self.assertEqual("Test.unitypackage", config.name) + self.assertFalse(config.export) + self.assertEqual("Foo/Bar", config.manifest_path) + self.assertEqual("Foo/Bar/Test_version-1.2.3_manifest.txt", + config.manifest_filename) + self.assertEqual( + self.expected_manifest_metadata_prebuild, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_LEGACY)) + self.assertEqual( + self.expected_upm_manifest_metadata_prebuild, + config.get_manifest_metadata( + export_unity_package.VERSION_HANDLER_MANIFEST_TYPE_UPM)) + self.assertEqual([], config.imports) + self.assertEqual([], config.includes) + self.assertEqual([re.compile("a/b/c")], config.exclude_paths) + self.assertEqual(set(["gvh", "gvh_version-1.2.3"]), config.labels) + self.assertEqual("1.2.3", config.version) + self.assertEqual("Test", config.package_name) + self.assertEqual("NiceName", config.common_package_display_name) + self.assertEqual( + {"name": "com.company.test", "description": ["Desc", "123"], + "display_name": "NiceName"}, + config.common_manifest) + self.assertEqual("com.company.test", config.common_package_name) + self.assertEqual("Desc123", config.common_package_description) + self.assertTrue(config.export_upm) + self.assertEqual("com.company.test-1.2.3.tgz", config.tarball_name) + self.assertEqual({"manifest": { + "unity": "2017.1" + }}, config.upm_package_config) + self.assertEqual({"unity": "2017.1"}, config.upm_manifest) + + def test_imports(self): + """Create a package configuration that includes other configs.""" + config = export_unity_package.PackageConfiguration( + self.project, + { + "name": "Test.unitypackage", + "imports": [ + { + "importer": "PluginImporter", + "paths": ["PlayServicesResolver/Editor/" + "Google.VersionHandler.*"] + }, + { + "importer": "DefaultImporter", + "paths": ["PlayServicesResolver/Editor/*_manifest*.txt"], + } + ] + }) + self.assertEqual( + [set(["PlayServicesResolver/Editor/Google.VersionHandler.*"]), + set(["PlayServicesResolver/Editor/*_manifest*.txt"])], + [asset_config.paths for asset_config in config.imports]) + + def test_write_manifest(self): + """Write a package manifest.""" + config = export_unity_package.PackageConfiguration( + self.project, + {"name": "Test.unitypackage", + "export": 0, + "manifest_path": "Foo/Bar"}) + output_directory = os.path.join(FLAGS.test_tmpdir, "manifest") + try: + os.makedirs(output_directory) + manifest_asset = config.write_manifest( + output_directory, + [export_unity_package.Asset( + "zebra/head.fbx", None, + export_unity_package.DEFAULT_METADATA_TEMPLATE), + export_unity_package.Asset( + "moose/tail.png", None, + export_unity_package.DEFAULT_METADATA_TEMPLATE), + export_unity_package.Asset( + "bear/paw.fplmesh", None, + export_unity_package.DEFAULT_METADATA_TEMPLATE)]) + self.assertEqual("Foo/Bar/Test_version-1.2.3_manifest.txt", + manifest_asset.filename) + self.assertEqual("Foo/Bar/Test_version-1.2.3_manifest.txt", + manifest_asset.filename_guid_lookup) + manifest_absolute_path = export_unity_package.posix_path(os.path.join( + output_directory, "Foo/Bar/Test_version-1.2.3_manifest.txt")) + self.assertEqual(manifest_absolute_path, + manifest_asset.filename_absolute) + self.assertFalse(manifest_asset.is_folder) + self.assertEqual(self.expected_manifest_metadata, + manifest_asset.importer_metadata) + with open(manifest_absolute_path, "rt") as manifest: + self.assertEqual( + "Assets/bear/paw.fplmesh\n" + "Assets/moose/tail.png\n" + "Assets/zebra/head.fbx\n", + manifest.read()) + finally: + shutil.rmtree(output_directory) + + def test_write_upm_manifest(self): + """Write a package manifest for UPM package.""" + config = export_unity_package.PackageConfiguration( + self.project, { + "name": "Test.unitypackage", + "export": 0, + "manifest_path": "Foo/Bar", + "common_manifest": { + "name": "com.company.test", + "display_name": "Test", + "description": "Test description", + "keywords": ["test keyword"], + "author": { + "name": "company", + "email": "someone@email.com", + "url": "https://test.company.com/" + } + }, + "export_upm": 1, + "upm_package_config": { + "manifest": { + "unity": "2017.1", + "dependencies": { + "com.company.dep": "1.6.8" + } + } + } + }) + expected_manifest = { + "name": "com.company.test", + "version": "1.2.3", + "displayName": "Test", + "description": "Test description", + "keywords": ["test keyword"], + "author": { + "name": "company", + "email": "someone@email.com", + "url": "https://test.company.com/" + }, + "unity": "2017.1", + "dependencies": { + "com.company.dep": "1.6.8" + } + } + output_directory = os.path.join(FLAGS.test_tmpdir, "manifest") + try: + os.makedirs(output_directory) + manifest_asset = config.write_upm_manifest(output_directory) + self.assertEqual("package.json", manifest_asset.filename) + self.assertEqual("com.company.test/package.json", + manifest_asset.filename_guid_lookup) + manifest_absolute_path = export_unity_package.posix_path( + os.path.join(output_directory, "package.json")) + self.assertEqual(manifest_absolute_path, manifest_asset.filename_absolute) + self.assertFalse(manifest_asset.is_folder) + self.assertEqual(self.expected_upm_manifest_metadata, + manifest_asset.importer_metadata) + with open(manifest_absolute_path, "rt") as manifest: + self.assertEqual(expected_manifest, json.loads(manifest.read())) + finally: + shutil.rmtree(output_directory) + + +class AssetTest(absltest.TestCase): + """Test the Asset class.""" + + def setUp(self): + """Create expected metadata.""" + super(AssetTest, self).setUp() + self.default_metadata = copy.deepcopy( + export_unity_package.DEFAULT_IMPORTER_METADATA_TEMPLATE) + self.default_metadata["labels"] = ["gvh", "gvh_version-1.2.3"] + self.staging_dir = os.path.join(FLAGS.test_tmpdir, "staging") + os.makedirs(self.staging_dir) + + self.assets_dir = os.path.join(TEST_DATA_PATH, "Assets") + self.asset_list = [ + export_unity_package.Asset("bar", "bar", self.default_metadata), + export_unity_package.Asset("foo/bar", "foo/bar", self.default_metadata), + ] + + def tearDown(self): + """Clean up the temporary directory.""" + super(AssetTest, self).tearDown() + delete_temporary_directory_contents() + + def test_init(self): + """Initialize an Asset instance.""" + asset = export_unity_package.Asset("libFooBar.so", "a/path/to/libFooBar.so", + collections.OrderedDict()) + self.assertEqual("libFooBar.so", asset.filename) + self.assertEqual("a/path/to/libFooBar.so", asset.filename_absolute) + self.assertEqual( + collections.OrderedDict([("labels", ["gvhp_exportpath-libFooBar.so"])]), + asset.importer_metadata) + self.assertEqual("libFooBar.so", asset.filename_guid_lookup) + self.assertEqual(False, asset.is_folder) + self.assertEqual(collections.OrderedDict(), + asset.importer_metadata_original) + + def test_init_override_guid_lookup(self): + """Initialize an Asset instance with overridden GUID Lookup filename.""" + asset = export_unity_package.Asset( + "libFooBar.so", + "a/path/to/libFooBar.so", + collections.OrderedDict(), + filename_guid_lookup="foo.bar/libFooBar.so") + self.assertEqual("libFooBar.so", asset.filename) + self.assertEqual("a/path/to/libFooBar.so", asset.filename_absolute) + self.assertEqual("foo.bar/libFooBar.so", asset.filename_guid_lookup) + self.assertEqual(False, asset.is_folder) + self.assertEqual( + collections.OrderedDict([("labels", ["gvhp_exportpath-libFooBar.so"])]), + asset.importer_metadata) + self.assertEqual(collections.OrderedDict(), + asset.importer_metadata_original) + + def test_init_folder(self): + """Initialize an folder Asset instance.""" + asset = export_unity_package.Asset( + "foo/bar", "foo/bar", collections.OrderedDict(), is_folder=True) + self.assertEqual("foo/bar", asset.filename) + self.assertEqual("foo/bar", asset.filename_absolute) + self.assertEqual("foo/bar", asset.filename_guid_lookup) + self.assertEqual(True, asset.is_folder) + self.assertEqual( + collections.OrderedDict([("labels", ["gvhp_exportpath-foo/bar"])]), + asset.importer_metadata) + self.assertEqual(collections.OrderedDict(), + asset.importer_metadata_original) + + def test_repr(self): + """Convert an asset to a string.""" + self.assertRegexMatch( + repr(export_unity_package.Asset("libFooBar.so", None, + self.default_metadata)), + ["filename=libFooBar.so metadata=.*gvh_version-1.2.3.*"]) + + def test_importer_metadata(self): + """Generate importer metadata for a path.""" + expected_metadata = copy.deepcopy(self.default_metadata) + expected_metadata["labels"] = sorted( + expected_metadata["labels"] + + ["gvhp_exportpath-Plugins/noarch/libFooBar.so"]) + asset = export_unity_package.Asset("Plugins/noarch/libFooBar.so", None, + self.default_metadata) + self.assertEqual(self.default_metadata, asset.importer_metadata_original) + self.assertEqual(expected_metadata, asset.importer_metadata) + + metadata_linuxlibname = copy.deepcopy(self.default_metadata) + metadata_linuxlibname["labels"] = sorted(metadata_linuxlibname["labels"] + + ["gvh_linuxlibname-FooBar"]) + + expected_metadata = copy.deepcopy(metadata_linuxlibname) + expected_metadata["labels"] = sorted( + expected_metadata["labels"] + + ["gvhp_exportpath-Plugins/x86/libFooBar.so"]) + asset = export_unity_package.Asset("Plugins/x86/libFooBar.so", None, + metadata_linuxlibname) + self.assertEqual(metadata_linuxlibname, asset.importer_metadata_original) + self.assertEqual(expected_metadata, asset.importer_metadata) + + expected_metadata = copy.deepcopy(metadata_linuxlibname) + expected_metadata["labels"] = sorted( + expected_metadata["labels"] + + ["gvhp_exportpath-Plugins/x86_64/libFooBar.so"]) + asset = export_unity_package.Asset("Plugins/x86_64/libFooBar.so", None, + metadata_linuxlibname) + self.assertEqual(metadata_linuxlibname, asset.importer_metadata_original) + self.assertEqual(expected_metadata, asset.importer_metadata) + + def test_add_labels_to_metadata(self): + """Add labels to importer metadata.""" + metadata = export_unity_package.Asset.add_labels_to_metadata( + self.default_metadata, set(["foo", "bar"])) + self.assertEqual( + ["bar", "foo", "gvh", "gvh_version-1.2.3"], + metadata["labels"]) + self.assertEqual(metadata, self.default_metadata) + + def test_add_labels_to_metadata_empty(self): + """Add an empty list of labels to empty metadata.""" + metadata = export_unity_package.Asset.add_labels_to_metadata( + collections.OrderedDict([("labels", [])]), set([])) + self.assertEqual(None, metadata.get("labels")) + + def test_disable_unsupported_platforms(self): + """Disable unsupported platforms for shared libraries.""" + all_platforms_enabled = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = all_platforms_enabled["PluginImporter"]["platformData"] + platform_data["Any"]["enabled"] = 1 + platform_data["Any"]["settings"]["CPU"] = "AnyCPU" + platform_data["Editor"]["enabled"] = 1 + platform_data["Editor"]["settings"]["CPU"] = "AnyCPU" + platform_data["OSXIntel"]["enabled"] = 1 + platform_data["OSXIntel"]["settings"]["CPU"] = "x86" + platform_data["OSXIntel64"]["enabled"] = 1 + platform_data["OSXIntel64"]["settings"]["CPU"] = "x86_64" + platform_data["OSXUniversal"]["enabled"] = 1 + platform_data["OSXUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["Linux"]["enabled"] = 1 + platform_data["Linux"]["settings"]["CPU"] = "x86" + platform_data["Linux64"]["enabled"] = 1 + platform_data["Linux64"]["settings"]["CPU"] = "x86_64" + platform_data["LinuxUniversal"]["enabled"] = 1 + platform_data["LinuxUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["Win"]["enabled"] = 1 + platform_data["Win"]["settings"]["CPU"] = "x86" + platform_data["Win64"]["enabled"] = 1 + platform_data["Win64"]["settings"]["CPU"] = "x86_64" + + expected_metadata = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = expected_metadata["PluginImporter"]["platformData"] + platform_data["Any"]["enabled"] = 0 + platform_data["Any"]["settings"]["CPU"] = "AnyCPU" + platform_data["Editor"]["enabled"] = 1 + platform_data["Editor"]["settings"]["CPU"] = "AnyCPU" + platform_data["OSXIntel"]["enabled"] = 1 + platform_data["OSXIntel"]["settings"]["CPU"] = "x86" + platform_data["OSXIntel64"]["enabled"] = 1 + platform_data["OSXIntel64"]["settings"]["CPU"] = "x86_64" + platform_data["OSXUniversal"]["enabled"] = 1 + platform_data["OSXUniversal"]["settings"]["CPU"] = "AnyCPU" + filename = "Plugins/x86/Foo/bar.bundle" + metadata = export_unity_package.Asset.disable_unsupported_platforms( + copy.deepcopy(all_platforms_enabled), filename) + self.assertEqual(expected_metadata, metadata) + expected_metadata["labels"] = [ + "gvhp_exportpath-Plugins/x86/Foo/bar.bundle"] + self.assertEqual(expected_metadata, export_unity_package.Asset( + filename, None, all_platforms_enabled).importer_metadata) + + expected_metadata = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = expected_metadata["PluginImporter"]["platformData"] + platform_data["Any"]["enabled"] = 0 + platform_data["Any"]["settings"]["CPU"] = "AnyCPU" + platform_data["Editor"]["enabled"] = 1 + platform_data["Editor"]["settings"]["CPU"] = "AnyCPU" + platform_data["Linux"]["enabled"] = 1 + platform_data["Linux"]["settings"]["CPU"] = "x86" + platform_data["Linux64"]["enabled"] = 1 + platform_data["Linux64"]["settings"]["CPU"] = "x86_64" + platform_data["LinuxUniversal"]["enabled"] = 1 + platform_data["LinuxUniversal"]["settings"]["CPU"] = "AnyCPU" + filename = "Assets/Plugins/x86_64/Foo/bar.so" + metadata = export_unity_package.Asset.disable_unsupported_platforms( + copy.deepcopy(all_platforms_enabled), filename) + self.assertEqual(expected_metadata, metadata) + expected_metadata["labels"] = [ + "gvhp_exportpath-Assets/Plugins/x86_64/Foo/bar.so"] + self.assertEqual(expected_metadata, export_unity_package.Asset( + filename, None, all_platforms_enabled).importer_metadata) + + expected_metadata = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = expected_metadata["PluginImporter"]["platformData"] + platform_data["Any"]["enabled"] = 0 + platform_data["Any"]["settings"]["CPU"] = "AnyCPU" + platform_data["Editor"]["enabled"] = 1 + platform_data["Editor"]["settings"]["CPU"] = "AnyCPU" + platform_data["Win"]["enabled"] = 1 + platform_data["Win"]["settings"]["CPU"] = "x86" + platform_data["Win64"]["enabled"] = 1 + platform_data["Win64"]["settings"]["CPU"] = "x86_64" + filename = "A/Path/To/Assets/Plugins/x86_64/Foo/bar.dll" + metadata = export_unity_package.Asset.disable_unsupported_platforms( + copy.deepcopy(all_platforms_enabled), filename) + self.assertEqual(expected_metadata, metadata) + expected_metadata["labels"] = [ + "gvhp_exportpath-A/Path/To/Assets/Plugins/x86_64/Foo/bar.dll"] + self.assertEqual(expected_metadata, export_unity_package.Asset( + filename, None, all_platforms_enabled).importer_metadata) + + expected_metadata = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = expected_metadata["PluginImporter"]["platformData"] + platform_data["Any"]["enabled"] = 0 + platform_data["Any"]["settings"]["CPU"] = "AnyCPU" + filename = "Plugins/Plugin.dll" + metadata = export_unity_package.Asset.disable_unsupported_platforms( + copy.deepcopy(expected_metadata), filename) + self.assertEqual(expected_metadata, metadata) + expected_metadata["labels"] = ["gvhp_exportpath-Plugins/Plugin.dll"] + self.assertEqual(expected_metadata, export_unity_package.Asset( + filename, None, expected_metadata).importer_metadata) + + def test_platform_data_get_entry(self): + """Retrieve an entry from a PluginImporter.platformData list.""" + unity_5_6_format = collections.OrderedDict([ + ("PluginImporter", collections.OrderedDict([ + ("serializedVersion", 2), + ("platformData", [ + collections.OrderedDict([ + ("data", collections.OrderedDict([ + ("first", collections.OrderedDict([ + ("Standalone", "Linux")])), + ("second", collections.OrderedDict([ + ("enabled", 1)]))]) + )]) + ]) + ])) + ]) + first, second = export_unity_package.Asset.platform_data_get_entry( + unity_5_6_format["PluginImporter"]["platformData"][0]) + self.assertEqual(collections.OrderedDict([("Standalone", "Linux")]), first) + self.assertEqual(collections.OrderedDict([("enabled", 1)]), second) + + unity_2017_format = collections.OrderedDict([ + ("PluginImporter", collections.OrderedDict([ + ("serializedVersion", 2), + ("platformData", [ + collections.OrderedDict([ + ("first", collections.OrderedDict([ + ("Standalone", "Linux")])), + ("second", collections.OrderedDict([ + ("enabled", 1)]))]) + ]) + ])) + ]) + first, second = export_unity_package.Asset.platform_data_get_entry( + unity_2017_format["PluginImporter"]["platformData"][0]) + self.assertEqual(collections.OrderedDict([("Standalone", "Linux")]), first) + self.assertEqual(collections.OrderedDict([("enabled", 1)]), second) + + def test_set_cpu_for_desktop_platforms_serializationv1(self): + """Set CPU field for enabled desktop platforms in v1 metadata format.""" + linux_enabled = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + linux_enabled["PluginImporter"]["platformData"]["Linux"]["enabled"] = 1 + expected_metadata = copy.deepcopy(linux_enabled) + expected_metadata["PluginImporter"]["platformData"]["Linux"]["settings"][ + "CPU"] = "x86" + linux_enabled_with_cpu = ( + export_unity_package.Asset.set_cpu_for_desktop_platforms(linux_enabled)) + self.assertEqual(expected_metadata, linux_enabled_with_cpu) + + def test_set_cpu_for_desktop_platforms_serializationv2(self): + """Set CPU field for enabled desktop platforms in v2 metadata format.""" + linux_enabled = collections.OrderedDict([ + ("PluginImporter", collections.OrderedDict([ + ("serializedVersion", 2), + ("platformData", [ + collections.OrderedDict([ + ("first", collections.OrderedDict([ + ("Standalone", "Linux")])), + ("second", collections.OrderedDict([ + ("enabled", 1)]))]) + ]) + ])) + ]) + expected_metadata = copy.deepcopy(linux_enabled) + expected_metadata["PluginImporter"]["platformData"][0]["second"][ + "settings"] = collections.OrderedDict([("CPU", "x86")]) + linux_enabled_with_cpu = ( + export_unity_package.Asset.set_cpu_for_desktop_platforms(linux_enabled)) + self.assertEqual(expected_metadata, linux_enabled_with_cpu) + + def test_set_cpu_for_android_serializationv1(self): + """Set CPU field for the enabled Android platform in v1 metadata format.""" + android_enabled = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + android_enabled["PluginImporter"]["platformData"]["Android"]["enabled"] = 1 + expected_metadata = copy.deepcopy(android_enabled) + expected_metadata["PluginImporter"]["platformData"]["Android"]["settings"][ + "CPU"] = "ARMv7" + android_enabled_with_cpu = ( + export_unity_package.Asset.set_cpu_for_android(android_enabled, "ARMv7")) + self.assertEqual(expected_metadata, android_enabled_with_cpu) + + def test_set_cpu_for_android_serializationv2(self): + """Set CPU field for the enabled Android platform in v2 metadata format.""" + android_enabled = collections.OrderedDict([ + ("PluginImporter", collections.OrderedDict([ + ("serializedVersion", 2), + ("platformData", [ + collections.OrderedDict([ + ("first", collections.OrderedDict([ + ("Android", "Android")])), + ("second", collections.OrderedDict([ + ("enabled", 1)]))]) + ]) + ])) + ]) + expected_metadata = copy.deepcopy(android_enabled) + expected_metadata["PluginImporter"]["platformData"][0]["second"][ + "settings"] = collections.OrderedDict([("CPU", "ARMv7")]) + android_enabled_with_cpu = ( + export_unity_package.Asset.set_cpu_for_android(android_enabled, "ARMv7")) + self.assertEqual(expected_metadata, android_enabled_with_cpu) + + def test_apply_any_platform_selection_serializationv1(self): + """Modify v1 importer metadata to enable all platforms.""" + # Enable all platforms. + any_platform_enabled = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = any_platform_enabled["PluginImporter"]["platformData"] + platform_data["Any"]["enabled"] = 1 + # Remove some platforms, these should be re-added to the metadata and + # enabled. + del platform_data["Win"] + del platform_data["Win64"] + del platform_data["WindowsStoreApps"] + del platform_data["iOS"] + del platform_data["tvOS"] + + expected_metadata = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + platform_data = expected_metadata["PluginImporter"]["platformData"] + platform_data["Android"]["enabled"] = 1 + platform_data["Any"]["enabled"] = 1 + platform_data["Editor"]["enabled"] = 1 + platform_data["Linux"]["enabled"] = 1 + platform_data["Linux64"]["enabled"] = 1 + platform_data["LinuxUniversal"]["enabled"] = 1 + platform_data["OSXIntel"]["enabled"] = 1 + platform_data["OSXIntel64"]["enabled"] = 1 + platform_data["OSXUniversal"]["enabled"] = 1 + platform_data["Web"]["enabled"] = 1 + platform_data["WebStreamed"]["enabled"] = 1 + platform_data["Win"]["enabled"] = 1 + platform_data["Win64"]["enabled"] = 1 + platform_data["WindowsStoreApps"]["enabled"] = 1 + platform_data["iOS"]["enabled"] = 1 + platform_data["tvOS"]["enabled"] = 1 + + all_platforms_enabled = ( + export_unity_package.Asset.apply_any_platform_selection( + any_platform_enabled)) + self.assertEqual(expected_metadata, all_platforms_enabled) + + # If Any is disabled, do not modify any platform states. + unmodified_metadata = ( + export_unity_package.Asset.apply_any_platform_selection( + copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE))) + self.assertEqual(export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE, + unmodified_metadata) + + def test_apply_any_platform_selection_serializationv2(self): + """Modify v2 importer metadata to enable all platforms.""" + def create_default_platform_data(target, name): + """Create a platform data entry. + + Args: + target: Name of the build target. + name: Name of the platform. + + Returns: + Ordered dictionary with the platformData metadata entry. + """ + return collections.OrderedDict([ + ("first", collections.OrderedDict([(target, name)])), + ("second", collections.OrderedDict([("enabled", 0)]))]) + + all_platform_data = [create_default_platform_data("Any", None)] + for platform in export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE[ + "PluginImporter"]["platformData"]: + if platform != "Any": + all_platform_data.append(create_default_platform_data( + export_unity_package.PLATFORM_TARGET_BY_PLATFORM[platform], + platform)) + + all_disabled = collections.OrderedDict([ + ("PluginImporter", collections.OrderedDict([ + ("serializedVersion", 2), + ("platformData", copy.deepcopy(all_platform_data)), + ])) + ]) + + # If "Any" isn't enabled, make sure the data isn't modified. + unmodified_metadata = ( + export_unity_package.Asset.apply_any_platform_selection( + copy.deepcopy(all_disabled))) + self.assertEqual(all_disabled, unmodified_metadata) + + # Enable the "Any" platform (first in the list) and remove some platforms + # from the list then verify all platforms are enabled. + any_enabled = copy.deepcopy(all_disabled) + platform_data = any_enabled["PluginImporter"]["platformData"] + platform_data[0]["second"]["enabled"] = 1 + any_enabled["PluginImporter"]["platformData"] = platform_data[:-3] + + all_enabled = export_unity_package.Asset.apply_any_platform_selection( + any_enabled) + expected_metadata = copy.deepcopy(all_disabled) + for config in expected_metadata["PluginImporter"]["platformData"]: + config["second"]["enabled"] = 1 + self.assertEqual(expected_metadata, all_enabled) + + def test_create_metadata(self): + """Test Asset.create_metadata().""" + asset = export_unity_package.Asset( + "Google.VersionHandler.dll", + os.path.join(self.assets_dir, + "PlayServicesResolver/Editor/Google.VersionHandler.dll"), + self.default_metadata) + + expected_metadata_path = os.path.join(self.staging_dir, + "Google.VersionHandler.dll.meta") + + asset.create_metadata(expected_metadata_path, + "06f6f385a4ad409884857500a3c04441", + timestamp=123456789) + + # Check metadata + with open(expected_metadata_path) as (metadata): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: 06f6f385a4ad409884857500a3c04441\n" + "labels:\n" + "- gvh\n" + "- gvh_version-1.2.3\n" + "- gvhp_exportpath-Google.VersionHandler.dll\n" + "timeCreated: 123456789\n" + "DefaultImporter:\n" + " userData:\n" + " assetBundleName:\n" + " assetBundleVariant:\n", metadata.read()) + + def test_create_metadata_folder(self): + """Test Asset.create_metadata().""" + asset = export_unity_package.Asset( + "foo/bar", "foo/bar", self.default_metadata, is_folder=True) + + expected_metadata_path = os.path.join(self.staging_dir, + "Google.VersionHandler.dll.meta") + + asset.create_metadata(expected_metadata_path, + "5187848eea9240faaec2deb7d66107db") + + # Check metadata + with open(expected_metadata_path) as (metadata): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: 5187848eea9240faaec2deb7d66107db\n" + "timeCreated: 0\n" + "folderAsset: true\n" + "DefaultImporter:\n" + " userData:\n" + " assetBundleName:\n" + " assetBundleVariant:\n", metadata.read()) + + def test_write(self): + """Test Asset.write().""" + asset_path = os.path.join( + self.assets_dir, + "PlayServicesResolver/Editor/Google.VersionHandler.dll") + + asset = export_unity_package.Asset("foo/bar/Google.VersionHandler.dll", + asset_path, self.default_metadata) + + expected_asset_path = os.path.join( + self.staging_dir, "06f6f385a4ad409884857500a3c04441/asset") + expected_metadata_path = os.path.join( + self.staging_dir, "06f6f385a4ad409884857500a3c04441/asset.meta") + expected_pathname_path = os.path.join( + self.staging_dir, "06f6f385a4ad409884857500a3c04441/pathname") + + asset.write(self.staging_dir, "06f6f385a4ad409884857500a3c04441", + timestamp=123456789) + + # Compare asset + self.assertTrue(filecmp.cmp(asset_path, expected_asset_path)) + + # Check metadata + with open(expected_metadata_path) as (metadata): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: 06f6f385a4ad409884857500a3c04441\n" + "labels:\n" + "- gvh\n" + "- gvh_version-1.2.3\n" + "- gvhp_exportpath-foo/bar/Google.VersionHandler.dll\n" + "timeCreated: 123456789\n" + "DefaultImporter:\n" + " userData:\n" + " assetBundleName:\n" + " assetBundleVariant:\n", metadata.read()) + + # Check pathname file + with open(expected_pathname_path) as (pathname): + self.assertEqual("Assets/foo/bar/Google.VersionHandler.dll", + pathname.read()) + + def test_write_folder(self): + """Test Asset.write() for folder asset.""" + + asset = export_unity_package.Asset( + "foo/bar", "foo/bar", self.default_metadata, is_folder=True) + + output_dir = asset.write(self.staging_dir, + "5187848eea9240faaec2deb7d66107db") + + # Should do nothing when writing a folder asset. + self.assertIsNone(output_dir) + + def test_write_upm(self): + """Test Asset.write_upm().""" + asset_path = os.path.join( + self.assets_dir, + "PlayServicesResolver/Editor/Google.VersionHandler.dll") + + asset = export_unity_package.Asset("foo/bar/Google.VersionHandler.dll", + asset_path, self.default_metadata) + + expected_asset_path = os.path.join( + self.staging_dir, "package/foo/bar/Google.VersionHandler.dll") + expected_metadata_path = os.path.join( + self.staging_dir, "package/foo/bar/Google.VersionHandler.dll.meta") + + asset.write_upm(self.staging_dir, "06f6f385a4ad409884857500a3c04441", + timestamp=123456789) + + # Compare asset + self.assertTrue(filecmp.cmp(asset_path, expected_asset_path)) + + # Check metadata + with open(expected_metadata_path) as (metadata): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: 06f6f385a4ad409884857500a3c04441\n" + "labels:\n" + "- gvh\n" + "- gvh_version-1.2.3\n" + "- gvhp_exportpath-foo/bar/Google.VersionHandler.dll\n" + "timeCreated: 123456789\n" + "DefaultImporter:\n" + " userData:\n" + " assetBundleName:\n" + " assetBundleVariant:\n", metadata.read()) + + def test_write_upm_folder(self): + """Test Asset.write_upm() for folder asset.""" + asset = export_unity_package.Asset( + "foo/bar", "foo/bar", self.default_metadata, is_folder=True) + + expected_folder_path = os.path.join(self.staging_dir, "package/foo/bar") + expected_metadata_path = os.path.join(self.staging_dir, + "package/foo/bar.meta") + + asset.write_upm(self.staging_dir, "5187848eea9240faaec2deb7d66107db") + + # Compare asset + self.assertTrue(os.path.isdir(expected_folder_path)) + + # Check metadata + with open(expected_metadata_path) as (metadata): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: 5187848eea9240faaec2deb7d66107db\n" + "timeCreated: 0\n" + "folderAsset: true\n" + "DefaultImporter:\n" + " userData:\n" + " assetBundleName:\n" + " assetBundleVariant:\n", metadata.read()) + + +class AssetConfigurationTest(absltest.TestCase): + """Test the AssetConfiguration class.""" + + def setUp(self): + """Create an empty package config and expected metadata.""" + super(AssetConfigurationTest, self).setUp() + self.package = export_unity_package.PackageConfiguration( + export_unity_package.ProjectConfiguration({}, set(), "1.2.3"), + {"name": "Test.unitypackage", "manifest_path": "Foo/Bar"}) + self.labels = set(["gvh", "gvh_version-1.2.3"]) + self.default_metadata = copy.deepcopy( + export_unity_package.DEFAULT_IMPORTER_METADATA_TEMPLATE) + self.default_metadata["labels"] = sorted(list(self.labels)) + self.plugin_metadata = copy.deepcopy( + export_unity_package.PLUGIN_IMPORTER_METADATA_TEMPLATE) + self.plugin_metadata["labels"] = sorted(list(self.labels)) + self.override_metadata = { + "PluginImporter": { + "platformData": { + "Editor": { + "enabled": 1 + } + } + } + } + + def test_create_empty(self): + """Create an empty config.""" + config = export_unity_package.AssetConfiguration(self.package, {}) + self.assertEqual(self.default_metadata, config.importer_metadata) + self.assertEqual(set(self.labels), config.labels) + self.assertEqual(set(), config.paths) + self.assertEqual({}, config.override_metadata) + + def test_labels(self): + """Test labels property.""" + self.assertEqual( + self.labels.union(set(["fun-label"])), + export_unity_package.AssetConfiguration(self.package, { + "importer": "DefaultImporter", + "labels": ["fun-label"] + }).labels) + + def test_paths(self): + """Test paths property.""" + self.assertEqual( + set([ + "foo/bar", + "bar", + ]), + export_unity_package.AssetConfiguration(self.package, { + "importer": "DefaultImporter", + "paths": [ + "bar", + "foo/bar", + ] + }).paths) + + def test_override_metadata(self): + """Test override_metadata property.""" + self.assertEqual( + self.override_metadata, + export_unity_package.AssetConfiguration( + self.package, { + "importer": "DefaultImporter", + "override_metadata": self.override_metadata + }).override_metadata) + + def test_override_metadata_upm(self): + """Test override_metadata_upm property.""" + self.assertEqual( + self.override_metadata, + export_unity_package.AssetConfiguration( + self.package, { + "importer": "DefaultImporter", + "override_metadata_upm": self.override_metadata + }).override_metadata_upm) + + def test_importer_metadata_default(self): + """Create default metadata.""" + self.assertEqual( + self.default_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "DefaultImporter"}).importer_metadata) + + def test_importer_metadata_invalid(self): + """Try to create metadata with an invalid importer.""" + with self.assertRaises(export_unity_package.ProjectConfigurationError): + unused_metadata = export_unity_package.AssetConfiguration( + self.package, {"importer": "InvalidImporter"}).importer_metadata + + def test_create_importer_metadata_editor_only(self): + """Create metadata that only targets the editor.""" + self.plugin_metadata["PluginImporter"]["platformData"]["Editor"][ + "enabled"] = 1 + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["Editor"]}).importer_metadata) + + def test_importer_metadata_android_only(self): + """Create metadata that only targets Android.""" + self.plugin_metadata["PluginImporter"]["platformData"]["Android"][ + "enabled"] = 1 + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["Android"]}).importer_metadata) + + def test_importer_metadata_android_only_armv7(self): + """Create metadata with ARMv7 CPU set.""" + self.plugin_metadata["PluginImporter"]["platformData"]["Android"][ + "enabled"] = 1 + self.plugin_metadata["PluginImporter"]["platformData"]["Android"][ + "settings"]["CPU"] = "ARMv7" + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["Android"], + "cpu": "ARMv7"}).importer_metadata) + + def test_importer_metadata_ios_only(self): + """Create metadata that only targets iOS.""" + self.plugin_metadata["PluginImporter"]["platformData"]["iOS"]["enabled"] = 1 + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["iOS"]}).importer_metadata) + + def test_importer_metadata_tvos_only(self): + """Create metadata that only targets tvOS.""" + self.plugin_metadata["PluginImporter"]["platformData"]["tvOS"]["enabled"] = 1 + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["tvOS"]}).importer_metadata) + + def test_importer_metadata_standalone_invalid_cpu(self): + """Create metadata with an invalid CPU.""" + with self.assertRaises(export_unity_package.ProjectConfigurationError): + unused_metadata = export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["Standalone"], + "cpu": "Crusoe"}).importer_metadata + + def test_importer_metadata_standalone_only_any_cpu(self): + """Create metadata that only targets standalone (desktop).""" + platform_data = self.plugin_metadata["PluginImporter"]["platformData"] + platform_data["Linux"]["enabled"] = 1 + platform_data["Linux"]["settings"]["CPU"] = "x86" + platform_data["Linux64"]["enabled"] = 1 + platform_data["Linux64"]["settings"]["CPU"] = "x86_64" + platform_data["LinuxUniversal"]["enabled"] = 1 + platform_data["LinuxUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["OSXIntel"]["enabled"] = 1 + platform_data["OSXIntel"]["settings"]["CPU"] = "x86" + platform_data["OSXIntel64"]["enabled"] = 1 + platform_data["OSXIntel64"]["settings"]["CPU"] = "x86_64" + platform_data["OSXUniversal"]["enabled"] = 1 + platform_data["OSXUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["Win"]["enabled"] = 1 + platform_data["Win"]["settings"]["CPU"] = "x86" + platform_data["Win64"]["enabled"] = 1 + platform_data["Win64"]["settings"]["CPU"] = "x86_64" + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["Standalone"]}).importer_metadata) + + def test_importer_metadata_standalone_only_x86(self): + """Create metadata that only targets standalone (desktop) x86.""" + platform_data = self.plugin_metadata["PluginImporter"]["platformData"] + platform_data["Linux"]["enabled"] = 1 + platform_data["Linux"]["settings"]["CPU"] = "x86" + platform_data["LinuxUniversal"]["enabled"] = 1 + platform_data["LinuxUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["OSXIntel"]["enabled"] = 1 + platform_data["OSXIntel"]["settings"]["CPU"] = "x86" + platform_data["OSXUniversal"]["enabled"] = 1 + platform_data["OSXUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["Win"]["enabled"] = 1 + platform_data["Win"]["settings"]["CPU"] = "x86" + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["Standalone"], + "cpu": "x86"}).importer_metadata) + + def test_importer_metadata_standalone_only_x86_64(self): + """Create metadata that only targets standalone (desktop) x86_64.""" + platform_data = self.plugin_metadata["PluginImporter"]["platformData"] + platform_data["Linux64"]["enabled"] = 1 + platform_data["Linux64"]["settings"]["CPU"] = "x86_64" + platform_data["LinuxUniversal"]["enabled"] = 1 + platform_data["LinuxUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["OSXIntel64"]["enabled"] = 1 + platform_data["OSXIntel64"]["settings"]["CPU"] = "x86_64" + platform_data["OSXUniversal"]["enabled"] = 1 + platform_data["OSXUniversal"]["settings"]["CPU"] = "AnyCPU" + platform_data["Win64"]["enabled"] = 1 + platform_data["Win64"]["settings"]["CPU"] = "x86_64" + self.assertEqual( + self.plugin_metadata, + export_unity_package.AssetConfiguration( + self.package, {"importer": "PluginImporter", + "platforms": ["Standalone"], + "cpu": "x86_64"}).importer_metadata) + + +class AssetPackageAndProjectFileOperationsTest(absltest.TestCase): + """Tests for file operation methods.""" + + def setUp(self): + """Unpack resources to a temporary directory.""" + super(AssetPackageAndProjectFileOperationsTest, self).setUp() + self.old_flag = FLAGS.enforce_semver + FLAGS.enforce_semver = False + self.assets_dir = os.path.join(TEST_DATA_PATH, "Assets") + self.staging_dir = os.path.join(FLAGS.test_tmpdir, "staging") + self.package = export_unity_package.PackageConfiguration( + export_unity_package.ProjectConfiguration({}, set(), None), + {"name": "Test.unitypackage"}) + os.makedirs(self.staging_dir) + self.version_handler_dll_metadata = collections.OrderedDict( + [("fileFormatVersion", 2), + ("guid", "06f6f385a4ad409884857500a3c04441"), + ("labels", ["gvh", "gvh_teditor", "gvh_v1.2.86.0", + "gvhp_exportpath-PlayServicesResolver/Editor/" + + "Google.VersionHandler.dll"]), + ("PluginImporter", collections.OrderedDict( + [("externalObjects", collections.OrderedDict()), + ("serializedVersion", 2), + ("iconMap", collections.OrderedDict()), + ("executionOrder", collections.OrderedDict()), + ("isPreloaded", 0), + ("isOverridable", 0), + ("platformData", [ + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Any", None)])), + ("second", collections.OrderedDict( + [("enabled", 0), + ("settings", collections.OrderedDict())]))]), + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Editor", "Editor")])), + ("second", collections.OrderedDict( + [("enabled", 1), + ("settings", collections.OrderedDict( + [("DefaultValueInitialized", True)]))]))]), + collections.OrderedDict( + [("first", collections.OrderedDict( + [("Windows Store Apps", "WindowsStoreApps")])), + ("second", collections.OrderedDict( + [("enabled", 0), + ("settings", collections.OrderedDict( + [("CPU", "AnyCPU")]))]))])]), + ("userData", None), + ("assetBundleName", None), + ("assetBundleVariant", None)]))]) + + self.expected_metadata_analytics = copy.deepcopy( + export_unity_package.DEFAULT_IMPORTER_METADATA_TEMPLATE) + self.expected_metadata_analytics["labels"] = [ + "gvhp_exportpath-Firebase/Plugins/Firebase.Analytics.dll"] + self.expected_metadata_app = copy.deepcopy( + export_unity_package.DEFAULT_IMPORTER_METADATA_TEMPLATE) + self.expected_metadata_app["labels"] = [ + "gvhp_exportpath-Firebase/Plugins/Firebase.App.dll"] + self.expected_metadata_auth = copy.deepcopy( + export_unity_package.DEFAULT_IMPORTER_METADATA_TEMPLATE) + self.expected_metadata_auth["labels"] = [ + "gvhp_exportpath-Firebase/Plugins/Firebase.Auth.dll"] + + def tearDown(self): + """Clean up the temporary directory.""" + super(AssetPackageAndProjectFileOperationsTest, self).tearDown() + FLAGS.enforce_semver = self.old_flag + delete_temporary_directory_contents() + + def test_find_files_no_wildcards(self): + """Walk a set of paths with no wildcards.""" + config = export_unity_package.AssetConfiguration( + self.package, + {"paths": ["Firebase/Plugins/Firebase.App.dll", + "Firebase/Plugins/Firebase.Analytics.dll"]}) + found_assets = config.find_assets([self.assets_dir]) + + self.assertCountEqual( + [("Firebase/Plugins/Firebase.Analytics.dll", + self.expected_metadata_analytics), + ("Firebase/Plugins/Firebase.App.dll", self.expected_metadata_app)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + def test_find_files_non_existant_file(self): + """Walk a set of paths with no wildcards.""" + config = export_unity_package.AssetConfiguration( + self.package, + {"paths": ["Firebase/Plugins/Firebase.Analytics.dll", + "Firebase/AFileThatDoesNotExist"]}) + found_assets = config.find_assets([self.assets_dir]) + self.assertEqual( + ["Firebase/Plugins/Firebase.Analytics.dll"], + [asset.filename for asset in found_assets]) + + def test_find_assets_using_directory(self): + """Walk a set of paths using a directory.""" + config = export_unity_package.AssetConfiguration( + self.package, {"paths": ["Firebase"]}) + found_assets = config.find_assets([self.assets_dir]) + self.assertCountEqual( + [("Firebase/Plugins/Firebase.Analytics.dll", + self.expected_metadata_analytics), + ("Firebase/Plugins/Firebase.App.dll", self.expected_metadata_app), + ("Firebase/Plugins/Firebase.Auth.dll", self.expected_metadata_auth)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + def test_find_assets_in_multiple_directories(self): + """Search multiple directories for assets.""" + config = export_unity_package.AssetConfiguration( + self.package, {"paths": ["Plugins/Firebase.Analytics.dll", + "Editor/Google.VersionHandler.dll"]}) + found_assets = config.find_assets( + [os.path.join(self.assets_dir, "Firebase"), + os.path.join(self.assets_dir, "PlayServicesResolver")]) + self.assertCountEqual( + [export_unity_package.posix_path(os.path.join( + self.assets_dir, "Firebase/Plugins/Firebase.Analytics.dll")), + export_unity_package.posix_path(os.path.join( + self.assets_dir, + "PlayServicesResolver/Editor/Google.VersionHandler.dll"))], + [asset.filename_absolute for asset in found_assets]) + + def test_find_assets_using_wildcard(self): + """Walk a set of paths using a wildcard.""" + config = export_unity_package.AssetConfiguration( + self.package, {"paths": ["Firebase/Plugins/Firebase.A*t*.dll"]}) + found_assets = config.find_assets([self.assets_dir]) + self.assertCountEqual( + [("Firebase/Plugins/Firebase.Analytics.dll", + self.expected_metadata_analytics), + ("Firebase/Plugins/Firebase.Auth.dll", self.expected_metadata_auth)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + def test_find_assets_with_metadata(self): + """Walk a set of paths using a wildcard with metadata.""" + config = export_unity_package.AssetConfiguration( + self.package, + {"paths": ["PlayServicesResolver/Editor/Google.VersionHandler.*"]}) + found_assets = config.find_assets([self.assets_dir]) + self.assertCountEqual( + [("PlayServicesResolver/Editor/Google.VersionHandler.dll", + self.version_handler_dll_metadata)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + def test_find_assets_with_override_metadata(self): + """Find an asset and override parts of its metadata.""" + config = export_unity_package.AssetConfiguration( + self.package, + collections.OrderedDict([ + ("paths", ["PlayServicesResolver/Editor/Google.VersionHandler.*"]), + ("override_metadata", collections.OrderedDict([ + ("PluginImporter", collections.OrderedDict([ + ("platformData", [collections.OrderedDict([ + ("first", collections.OrderedDict([ + ("Editor", "Editor")])), + ("second", collections.OrderedDict([ + ("enabled", 0)])) + ])]) + ])) + ])) + ])) + expected_metadata = copy.deepcopy(self.version_handler_dll_metadata) + expected_metadata["PluginImporter"]["platformData"][1]["second"][ + "enabled"] = 0 + + # Metadata with find_assets(for_upm=False) should be overridden. + found_assets = config.find_assets([self.assets_dir]) + self.assertCountEqual( + [("PlayServicesResolver/Editor/Google.VersionHandler.dll", + expected_metadata)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + # Metadata with find_assets(for_upm=True) should be overridden. + found_assets_upm = config.find_assets([self.assets_dir], for_upm=True) + self.assertCountEqual( + [("PlayServicesResolver/Editor/Google.VersionHandler.dll", + expected_metadata)], + [(asset.filename, asset.importer_metadata) for asset in found_assets_upm + ]) + + def test_find_assets_with_override_metadata_upm(self): + """Find an asset and override parts of its metadata.""" + config = export_unity_package.AssetConfiguration( + self.package, + collections.OrderedDict([ + ("paths", ["PlayServicesResolver/Editor/Google.VersionHandler.*"]), + ("override_metadata_upm", + collections.OrderedDict([ + ("PluginImporter", + collections.OrderedDict([("platformData", [ + collections.OrderedDict([ + ("first", + collections.OrderedDict([("Editor", "Editor")])), + ("second", collections.OrderedDict([("enabled", 0)])) + ]) + ])])) + ])) + ])) + + # Metadata with find_assets(for_upm=False) should remain unchanged. + found_assets = config.find_assets([self.assets_dir]) + self.assertCountEqual( + [("PlayServicesResolver/Editor/Google.VersionHandler.dll", + self.version_handler_dll_metadata)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + # Metadata with find_assets(for_upm=True) should be overridden. + expected_metadata = copy.deepcopy(self.version_handler_dll_metadata) + expected_metadata["PluginImporter"]["platformData"][1]["second"][ + "enabled"] = 0 + found_assets_upm = config.find_assets([self.assets_dir], for_upm=True) + self.assertCountEqual( + [("PlayServicesResolver/Editor/Google.VersionHandler.dll", + expected_metadata)], + [(asset.filename, asset.importer_metadata) for asset in found_assets_upm + ]) + + def test_find_assets_with_exclusions(self): + """Find assets for a package with a subset of files excluded.""" + config_json = { + "packages": [ + {"name": "FirebaseAppAndAuth.unitypackage", + "imports": [ + {"paths": [ + "Firebase/Plugins/Firebase.App.dll", + "Firebase/Plugins/Firebase.Auth.dll" + ]} + ]}, + {"name": "PlayServicesResolver.unitypackage", + "imports": [ + {"paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll" + ]} + ]}, + {"name": "FirebaseAnalytics.unitypackage", + "imports": [ + {"paths": [ + "Firebase/Plugins/Firebase.Analytics.dll" + ]} + ], + "exclude_paths": [r".*\.Auth\.dll$"], + "includes": ["FirebaseAppAndAuth.unitypackage", + "PlayServicesResolver.unitypackage"] + } + ] + } + config = export_unity_package.ProjectConfiguration(config_json, set(), None) + package = config.packages_by_name["FirebaseAnalytics.unitypackage"] + found_assets = package.find_assets([self.assets_dir]) + self.assertCountEqual( + [("Firebase/Plugins/Firebase.Analytics.dll", + self.expected_metadata_analytics), + ("Firebase/Plugins/Firebase.App.dll", self.expected_metadata_app), + ("PlayServicesResolver/Editor/Google.VersionHandler.dll", + self.version_handler_dll_metadata)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + def test_find_assets_via_includes(self): + """Find assets by transitively searching included packages.""" + config_json = { + "packages": [ + {"name": "FirebaseApp.unitypackage", + "imports": [{"paths": ["Firebase/Plugins/Firebase.App.dll"]}]}, + {"name": "PlayServicesResolver.unitypackage", + "imports": [ + {"paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll" + ]} + ]}, + {"name": "FirebaseAnalytics.unitypackage", + "imports": [ + {"paths": [ + "Firebase/Plugins/Firebase.Analytics.dll" + ]} + ], + "includes": ["FirebaseApp.unitypackage", + "PlayServicesResolver.unitypackage"] + } + ] + } + config = export_unity_package.ProjectConfiguration(config_json, set(), None) + package = config.packages_by_name["FirebaseAnalytics.unitypackage"] + found_assets = package.find_assets([self.assets_dir]) + + self.assertCountEqual( + [("Firebase/Plugins/Firebase.Analytics.dll", + self.expected_metadata_analytics), + ("Firebase/Plugins/Firebase.App.dll", self.expected_metadata_app), + ("PlayServicesResolver/Editor/Google.VersionHandler.dll", + self.version_handler_dll_metadata)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + def test_find_assets_via_includes_with_conflicting_metadata(self): + """Find assets with conflicting metadata.""" + config_json = { + "packages": [ + {"name": "PlayServicesResolver.unitypackage", + "imports": [ + {"paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll" + ]} + ]}, + {"name": "PlayServicesResolverConflicting.unitypackage", + "imports": [ + {"labels": ["conflicting"], + "paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll" + ] + } + ] + }, + {"name": "FirebaseAnalytics.unitypackage", + "imports": [ + {"paths": ["Firebase/Plugins/Firebase.Analytics.dll"]} + ], + "includes": [ + "PlayServicesResolver.unitypackage", + "PlayServicesResolverConflicting.unitypackage" + ] + } + ] + } + + config = export_unity_package.ProjectConfiguration(config_json, set(), None) + package = config.packages_by_name["FirebaseAnalytics.unitypackage"] + with self.assertRaises(export_unity_package.ProjectConfigurationError) as ( + context): + package.find_assets([self.assets_dir]) + self.assertRegexMatch( + str(context.exception), + [r"File .*Google\.VersionHandler\.dll imported with different import " + r"settings in .*PlayServicesResolver\.unitypackage', " + r".*PlayServicesResolverConflicting\.unitypackage'"]) + + def test_find_assets_for_upm(self): + """Find assets for UPM package. + + This should exclude assets from included packages. + """ + config_json = { + "packages": [{ + "name": "FirebaseApp.unitypackage", + "imports": [{ + "paths": ["Firebase/Plugins/Firebase.App.dll"] + }] + }, { + "name": + "PlayServicesResolver.unitypackage", + "imports": [{ + "paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll" + ] + }] + }, { + "name": + "FirebaseAnalytics.unitypackage", + "imports": [{ + "paths": ["Firebase/Plugins/Firebase.Analytics.dll"] + }], + "includes": [ + "FirebaseApp.unitypackage", "PlayServicesResolver.unitypackage" + ] + }] + } + config = export_unity_package.ProjectConfiguration(config_json, set(), None) + package = config.packages_by_name["FirebaseAnalytics.unitypackage"] + found_assets = package.find_assets([self.assets_dir], for_upm=True) + + self.assertCountEqual( + [("Firebase/Plugins/Firebase.Analytics.dll", + self.expected_metadata_analytics)], + [(asset.filename, asset.importer_metadata) for asset in found_assets]) + + def test_asset_write_metadata(self): + """Write asset metadata to a file.""" + metadata_filename = os.path.join(FLAGS.test_tmpdir, "metadata") + export_unity_package.Asset.write_metadata( + metadata_filename, + [collections.OrderedDict( + [("someMetadata", None), + ("otherData", "foo") + ]), + collections.OrderedDict( + [("packageInfo", + collections.OrderedDict( + [("version", 1), + ("format", "max")])) + ]), + collections.OrderedDict([("someMetadata", 1)]), + collections.OrderedDict( + [("packageInfo", + collections.OrderedDict( + [("format", "ultra"), + ("enabled", 1)])) + ]), + ]) + with open(metadata_filename) as metadata_file: + self.assertEqual("someMetadata: 1\n" + "otherData: foo\n" + "packageInfo:\n" + " version: 1\n" + " format: ultra\n" + " enabled: 1\n", + metadata_file.read()) + + def test_asset_write(self): + """Add an asset to a plugin archive staging area.""" + asset = export_unity_package.Asset( + "Firebase/Plugins/Firebase.App.dll", + os.path.join(self.assets_dir, "Firebase/Plugins/Firebase.App.dll"), + collections.OrderedDict( + [("someMetadata", 1), + ("otherData", "foo"), + ("packageInfo", + collections.OrderedDict( + [("version", 1)]))])) + asset_dir = asset.write(self.staging_dir, + "fa42a2182ad64806b8f78e9de4cc4f78", 123456789) + self.assertTrue(os.path.join(self.staging_dir, + "fa42a2182ad64806b8f78e9de4cc4f78"), + asset_dir) + self.assertTrue(filecmp.cmp(os.path.join(asset_dir, "asset"), + asset.filename_absolute)) + + with open(os.path.join(asset_dir, "pathname")) as ( + asset_pathname_file): + self.assertEqual("Assets/Firebase/Plugins/Firebase.App.dll", + asset_pathname_file.read()) + + with open(os.path.join(asset_dir, "asset.meta")) as ( + asset_metadata_file): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: fa42a2182ad64806b8f78e9de4cc4f78\n" + "labels:\n" + "- gvhp_exportpath-Firebase/Plugins/Firebase.App.dll\n" + "timeCreated: 123456789\n" + "someMetadata: 1\n" + "otherData: foo\n" + "packageInfo:\n" + " version: 1\n", + asset_metadata_file.read()) + + def test_package_create_archive(self): + """Create a unitypackage archive.""" + test_case_dir = os.path.join(FLAGS.test_tmpdir, "test_create_archive") + os.makedirs(test_case_dir) + use_tar = export_unity_package.FLAGS.use_tar + try: + # Disable use of tar command line application until this is reproducible on + # macOS. + export_unity_package.FLAGS.use_tar = (platform.system() != "Darwin") + + archive_dir = os.path.join(test_case_dir, "archive_dir") + os.makedirs(archive_dir) + # Create some files to archive. + test_files = {"a/b/c.txt": "hello", + "d/e.txt": "world"} + for filename, contents in test_files.items(): + input_filename = os.path.join(os.path.join(archive_dir, filename)) + os.makedirs(os.path.dirname(input_filename)) + with open(input_filename, "wt") as input_file: + input_file.write(contents) + + # Create an archive. + archive_filename = os.path.join(test_case_dir, "archive.unitypackage") + export_unity_package.PackageConfiguration.create_archive(archive_filename, + archive_dir, 0) + + # Unpack the archive and make sure the expected files were stored. + with tarfile.open(archive_filename, "r:gz") as archive_file: + self.assertCountEqual( + ["a", "a/b", "a/b/c.txt", "d", "d/e.txt"], + archive_file.getnames()) + for filename in test_files: + embedded_file = archive_file.extractfile(filename) + self.assertEqual(test_files[filename], + embedded_file.read().decode("utf8")) + + # Touch all files in the archive. + for filename in test_files: + input_filename = os.path.join(os.path.join(archive_dir, filename)) + file_stat = os.stat(input_filename) + os.utime(input_filename, (file_stat.st_atime, file_stat.st_mtime - 60)) + + # Create another archive. + other_archive_filename = os.path.join(test_case_dir, + "archive2.unitypackage") + os.rename(archive_filename, other_archive_filename) + # Wait before writing another archive so that any potential changes to + # timestamps can be written. + time.sleep(1) + # We create the archive with the original filename as the filename is + # embedded in the archive. + export_unity_package.PackageConfiguration.create_archive(archive_filename, + archive_dir, 0) + self.assertTrue(filecmp.cmp(archive_filename, other_archive_filename)) + + finally: + shutil.rmtree(test_case_dir) + export_unity_package.FLAGS.use_tar = use_tar + + def test_package_write(self): + """Write a .unitypackage file.""" + project = export_unity_package.ProjectConfiguration( + {"packages": [ + {"name": "FirebaseApp.unitypackage", + "manifest_path": "Firebase/Editor", + "imports": [ + {"paths": [ + "Firebase/Plugins/Firebase.App.dll", + "PlayServicesResolver/Editor/Google.VersionHandler.dll", + ]} + ]} + ]}, set(), "1.0.0") + package = project.packages_by_name["FirebaseApp.unitypackage"] + unitypackage = package.write( + export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), + { + "1.0.0": { + "Firebase/Editor/FirebaseApp_version-1.0.0_manifest.txt": + "08d62f799cbd4b02a3ff77313706a3c0", + "Firebase/Plugins/Firebase.App.dll": + "7311924048bd457bac6d713576c952da" + } + }, "1.0.0"), + [self.assets_dir], self.staging_dir, 0) + + self.assertEqual(os.path.join(self.staging_dir, "FirebaseApp.unitypackage"), + unitypackage) + + with tarfile.open(unitypackage, "r:gz") as unitypackage_file: + self.assertCountEqual( + ["06f6f385a4ad409884857500a3c04441", + "06f6f385a4ad409884857500a3c04441/asset", + "06f6f385a4ad409884857500a3c04441/asset.meta", + "06f6f385a4ad409884857500a3c04441/pathname", + "08d62f799cbd4b02a3ff77313706a3c0", + "08d62f799cbd4b02a3ff77313706a3c0/asset", + "08d62f799cbd4b02a3ff77313706a3c0/asset.meta", + "08d62f799cbd4b02a3ff77313706a3c0/pathname", + "7311924048bd457bac6d713576c952da", + "7311924048bd457bac6d713576c952da/asset", + "7311924048bd457bac6d713576c952da/asset.meta", + "7311924048bd457bac6d713576c952da/pathname"], + unitypackage_file.getnames()) + unitypackage_file.extractall(self.staging_dir) + self.assertTrue(filecmp.cmp( + os.path.join(self.assets_dir, "Firebase/Plugins/Firebase.App.dll"), + os.path.join(self.staging_dir, + "7311924048bd457bac6d713576c952da/asset"))) + with open(os.path.join(self.staging_dir, + "08d62f799cbd4b02a3ff77313706a3c0/asset"), + "rt") as manifest: + self.assertEqual( + "Assets/Firebase/Plugins/Firebase.App.dll\n" + "Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll\n", + manifest.read()) + with open(os.path.join( + self.staging_dir, "06f6f385a4ad409884857500a3c04441/asset.meta")) as ( + metadata): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: 06f6f385a4ad409884857500a3c04441\n" + "labels:\n" + "- gvh\n" + "- gvh_teditor\n" + "- gvh_v1.2.86.0\n" + "- gvh_version-1.0.0\n" + "- gvhp_exportpath-PlayServicesResolver/Editor/" + "Google.VersionHandler.dll\n" + "timeCreated: 0\n" + "PluginImporter:\n" + " externalObjects: {}\n" + " serializedVersion: 2\n" + " iconMap: {}\n" + " executionOrder: {}\n" + " isPreloaded: 0\n" + " isOverridable: 0\n" + " platformData:\n" + " - first:\n" + " Any:\n" + " second:\n" + " enabled: 0\n" + " settings: {}\n" + " - first:\n" + " Editor: Editor\n" + " second:\n" + " enabled: 1\n" + " settings:\n" + " DefaultValueInitialized: true\n" + " - first:\n" + " Windows Store Apps: WindowsStoreApps\n" + " second:\n" + " enabled: 0\n" + " settings:\n" + " CPU: AnyCPU\n" + " userData:\n" + " assetBundleName:\n" + " assetBundleVariant:\n", metadata.read()) + + def test_package_write_with_includes_and_export(self): + """Write a .unitypackage file.""" + # This is a slighty more complicated case + # FirebaseAuth.unitypackage: + # export=1, generate manifest, includes=[FirebaseApp, FirebaseAnalytics] + # FirebaseAnalytics.unitypackage: + # export=1, generate manifest, includes=[FirebaseApp] + # FirebaseApp.unitypackage: + # export=0, generate manifest, includes=[VersionHandler] + # VersionHandler.unitypackage: + # export=0, no manifest, includes=[] + project = export_unity_package.ProjectConfiguration( + { + "packages": [{ + "name": "FirebaseApp.unitypackage", + "manifest_path": "Firebase/Editor", + "imports": [{ + "paths": ["Firebase/Plugins/Firebase.App.dll",] + }], + "includes": ["VersionHandler.unitypackage"], + "export": 0 + }, { + "name": "VersionHandler.unitypackage", + "imports": [{ + "paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll", + ] + }], + "export": 0 + }, { + "name": "FirebaseAnalytics.unitypackage", + "manifest_path": "Firebase/Editor", + "imports": [{ + "paths": ["Firebase/Plugins/Firebase.Analytics.dll",] + }], + "includes": ["FirebaseApp.unitypackage"] + }, { + "name": + "FirebaseAuth.unitypackage", + "manifest_path": + "Firebase/Editor", + "imports": [{ + "paths": ["Firebase/Plugins/Firebase.Auth.dll",] + }], + "includes": [ + "FirebaseApp.unitypackage", "FirebaseAnalytics.unitypackage" + ] + }] + }, set(), "1.0.0") + package = project.packages_by_name["FirebaseAuth.unitypackage"] + unitypackage = package.write( + export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), { + "1.0.0": { + "Firebase/Editor/FirebaseApp_version-1.0.0_manifest.txt": + "08d62f799cbd4b02a3ff77313706a3c0", + ("Firebase/Editor/" + "FirebaseAnalytics_version-1.0.0_manifest.txt"): + "4a3f361c622e4b88b6f61a126cc8083d", + "Firebase/Editor/FirebaseAuth_version-1.0.0_manifest.txt": + "2b2a3eb537894428a96778fef31996e2", + "Firebase/Plugins/Firebase.App.dll": + "7311924048bd457bac6d713576c952da", + "Firebase/Plugins/Firebase.Analytics.dll": + "816270c2a2a348e59cb9b7b096a24f50", + "Firebase/Plugins/Firebase.Auth.dll": + "275bd6b96a28470986154b9a995e191c" + } + }, "1.0.0"), [self.assets_dir], self.staging_dir, 0) + + self.assertEqual( + os.path.join(self.staging_dir, "FirebaseAuth.unitypackage"), + unitypackage) + + with tarfile.open(unitypackage, "r:gz") as unitypackage_file: + self.assertCountEqual([ + "06f6f385a4ad409884857500a3c04441", + "06f6f385a4ad409884857500a3c04441/asset", + "06f6f385a4ad409884857500a3c04441/asset.meta", + "06f6f385a4ad409884857500a3c04441/pathname", + "08d62f799cbd4b02a3ff77313706a3c0", + "08d62f799cbd4b02a3ff77313706a3c0/asset", + "08d62f799cbd4b02a3ff77313706a3c0/asset.meta", + "08d62f799cbd4b02a3ff77313706a3c0/pathname", + "4a3f361c622e4b88b6f61a126cc8083d", + "4a3f361c622e4b88b6f61a126cc8083d/asset", + "4a3f361c622e4b88b6f61a126cc8083d/asset.meta", + "4a3f361c622e4b88b6f61a126cc8083d/pathname", + "2b2a3eb537894428a96778fef31996e2", + "2b2a3eb537894428a96778fef31996e2/asset", + "2b2a3eb537894428a96778fef31996e2/asset.meta", + "2b2a3eb537894428a96778fef31996e2/pathname", + "7311924048bd457bac6d713576c952da", + "7311924048bd457bac6d713576c952da/asset", + "7311924048bd457bac6d713576c952da/asset.meta", + "7311924048bd457bac6d713576c952da/pathname", + "816270c2a2a348e59cb9b7b096a24f50", + "816270c2a2a348e59cb9b7b096a24f50/asset", + "816270c2a2a348e59cb9b7b096a24f50/asset.meta", + "816270c2a2a348e59cb9b7b096a24f50/pathname", + "275bd6b96a28470986154b9a995e191c", + "275bd6b96a28470986154b9a995e191c/asset", + "275bd6b96a28470986154b9a995e191c/asset.meta", + "275bd6b96a28470986154b9a995e191c/pathname" + ], unitypackage_file.getnames()) + unitypackage_file.extractall(self.staging_dir) + self.assertTrue( + filecmp.cmp( + os.path.join(self.assets_dir, + "Firebase/Plugins/Firebase.App.dll"), + os.path.join(self.staging_dir, + "7311924048bd457bac6d713576c952da/asset"))) + self.assertTrue( + filecmp.cmp( + os.path.join(self.assets_dir, + "Firebase/Plugins/Firebase.Auth.dll"), + os.path.join(self.staging_dir, + "275bd6b96a28470986154b9a995e191c/asset"))) + self.assertTrue( + filecmp.cmp( + os.path.join(self.assets_dir, + "Firebase/Plugins/Firebase.Analytics.dll"), + os.path.join(self.staging_dir, + "816270c2a2a348e59cb9b7b096a24f50/asset"))) + # Verify FirebaseApp_version-1.0.0_manifest.txt + with open( + os.path.join(self.staging_dir, + "08d62f799cbd4b02a3ff77313706a3c0/asset"), + "rt") as manifest: + self.assertEqual( + "Assets/Firebase/Plugins/Firebase.App.dll\n" + "Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll\n", + manifest.read()) + # Verify FirebaseAnalytics_version-1.0.0_manifest.txt + with open( + os.path.join(self.staging_dir, + "4a3f361c622e4b88b6f61a126cc8083d/asset"), + "rt") as manifest: + self.assertEqual( + "Assets/Firebase/Plugins/Firebase.Analytics.dll\n" + "Assets/Firebase/Plugins/Firebase.App.dll\n" + "Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll\n", + manifest.read()) + # Verify FirebaseAuth_version-1.0.0_manifest.txt + with open( + os.path.join(self.staging_dir, + "2b2a3eb537894428a96778fef31996e2/asset"), + "rt") as manifest: + self.assertEqual( + "Assets/Firebase/Plugins/Firebase.Analytics.dll\n" + "Assets/Firebase/Plugins/Firebase.App.dll\n" + "Assets/Firebase/Plugins/Firebase.Auth.dll\n" + "Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll\n", + manifest.read()) + + def test_package_write_upm(self): + """Write a .tgz file.""" + # This is a slightly complicated case + # play-services-resolver: + # export_upm=1, includes=[ios-resolver, jar-resolver] + # Google.VersionHandlerImpl_*.dll overridden to be enabled in editor + # ios-resolver: + # export_upm=1 + # jar-resolver: + # export_upm=0 + + expected_override_metadata = {"Editor": {"enabled": 1}} + + project = export_unity_package.ProjectConfiguration( + { + "packages": [{ + "name": "jar-resolver.unitypackage", + "imports": [{ + "paths": [ + "PlayServicesResolver/Editor/Google.JarResolver_*.dll", + ] + }], + "common_manifest": { + "name": "com.google.jar-resolver", + }, + }, { + "name": "ios-resolver.unitypackage", + "imports": [{ + "paths": [ + "PlayServicesResolver/Editor/Google.IOSResolver_*.dll", + ] + }], + "common_manifest": { + "name": "com.google.ios-resolver", + }, + "export_upm": 1, + }, { + "name": "play-services-resolver.unitypackage", + "imports": [{ + "paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll", + ] + }, { + "paths": [ + "PlayServicesResolver/Editor/" + "Google.VersionHandlerImpl_*.dll", + ], + "override_metadata_upm": { + "PluginImporter": { + "platformData": expected_override_metadata, + } + }, + }], + "manifest_path": "PlayServicesResolver/Editor", + "readme": "PlayServicesResolver/Editor/README.md", + "changelog": "PlayServicesResolver/Editor/CHANGELOG.md", + "license": "PlayServicesResolver/Editor/LICENSE", + "documentation": "PlayServicesResolver/Doc", + "includes": + ["ios-resolver.unitypackage", "jar-resolver.unitypackage"], + "common_manifest": { + "name": "com.google.play-services-resolver", + "display_name": "Play Services Resolver", + }, + "export_upm": 1, + "upm_package_config": { + "manifest": { + "unity": "2017.1", + "dependencies": { + "com.some.third-party-package": "1.2.3" + }, + } + } + }] + }, set(), "1.0.0") + package = project.packages_by_name["play-services-resolver.unitypackage"] + + expected_tarball_name = "com.google.play-services-resolver-1.0.0.tgz" + + self.assertEqual(expected_tarball_name, package.tarball_name) + + unitypackage = package.write_upm( + export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), { + "1.0.0": { + "com.google.play-services-resolver/README.md": + "baa27a4c0385454899a759d9852966b7", + "com.google.play-services-resolver/CHANGELOG.md": + "000ce82791494e44b04c7a6f9a31151c", + "com.google.play-services-resolver/LICENSE.md": + "94717c1d977f445baed18e00605e3d7c", + "PlayServicesResolver/Editor/" + "play-services-resolver_version-1.0.0_manifest.txt": + "353f6aace2cd42adb1343fc6a808f62e", + "com.google.play-services-resolver/package.json": + "782a38c5f19e4bb99e927976c8daa9ac", + "com.google.play-services-resolver/PlayServicesResolver": + "fa7daf703ad1430dad0cd8b764e5e6d2", + "com.google.play-services-resolver/PlayServicesResolver/" + "Editor": + "2334cd7684164851a8a53db5bd5923ca", + } + }, "1.0.0"), [self.assets_dir], self.staging_dir, 0) + + expected_manifest = { + "name": "com.google.play-services-resolver", + "displayName": "Play Services Resolver", + "version": "1.0.0", + "unity": "2017.1", + "keywords": [ + "vh-name:play-services-resolver", + "vh-name:Play Services Resolver" + ], + "dependencies": { + "com.some.third-party-package": "1.2.3", + "com.google.ios-resolver": "1.0.0" + }, + } + self.assertEqual( + os.path.join(self.staging_dir, expected_tarball_name), unitypackage) + + with tarfile.open(unitypackage, "r:gz") as unitypackage_file: + # Check included files. + self.assertCountEqual([ + "package", + "package/package.json", + "package/package.json.meta", + "package/README.md", + "package/README.md.meta", + "package/CHANGELOG.md", + "package/CHANGELOG.md.meta", + "package/LICENSE.md", + "package/LICENSE.md.meta", + "package/PlayServicesResolver", + "package/PlayServicesResolver.meta", + "package/PlayServicesResolver/Editor", + "package/PlayServicesResolver/Editor.meta", + "package/PlayServicesResolver/Editor/Google.VersionHandler.dll", + "package/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta", + "package/PlayServicesResolver/Editor/" + "play-services-resolver_version-1.0.0_manifest.txt", + "package/PlayServicesResolver/Editor/" + "play-services-resolver_version-1.0.0_manifest.txt.meta", + "package/PlayServicesResolver/Editor/" + "Google.VersionHandlerImpl_v1.2.87.0.dll", + "package/PlayServicesResolver/Editor/" + "Google.VersionHandlerImpl_v1.2.87.0.dll.meta", + "package/Documentation~", + "package/Documentation~/index.md", + ], unitypackage_file.getnames()) + unitypackage_file.extractall(self.staging_dir) + + self.assertTrue( + filecmp.cmp( + os.path.join( + self.assets_dir, + "PlayServicesResolver/Editor/Google.VersionHandler.dll"), + os.path.join( + self.staging_dir, "package/PlayServicesResolver/Editor/" + "Google.VersionHandler.dll"))) + + self.assertTrue( + filecmp.cmp( + os.path.join( + self.assets_dir, + "PlayServicesResolver/Doc/index.md"), + os.path.join( + self.staging_dir, "package/Documentation~/index.md"))) + + # Check package.json + with open(os.path.join(self.staging_dir, "package/package.json"), + "rt") as manifest: + self.assertEqual(expected_manifest, json.loads(manifest.read())) + + # Check folder metadata + with open( + os.path.join(self.staging_dir, + "package/PlayServicesResolver.meta")) as (metadata): + self.assertEqual( + "fileFormatVersion: 2\n" + "guid: fa7daf703ad1430dad0cd8b764e5e6d2\n" + "timeCreated: 0\n" + "folderAsset: true\n" + "DefaultImporter:\n" + " userData:\n" + " assetBundleName:\n" + " assetBundleVariant:\n", metadata.read()) + + # Check overridden metadata + with open( + os.path.join( + self.staging_dir, "package/PlayServicesResolver/Editor/" + "Google.VersionHandlerImpl_v1.2.87.0.dll.meta")) as (metadata): + serializer = export_unity_package.YamlSerializer() + yaml_dict = serializer.load(metadata.read()) + self.assertEqual(expected_override_metadata, + yaml_dict["PluginImporter"]["platformData"]) + + def test_package_write_upm_documentation_as_file(self): + """Test write_upm() with documentation path as a file.""" + project = export_unity_package.ProjectConfiguration( + { + "packages": [{ + "name": "play-services-resolver.unitypackage", + "imports": [{ + "paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll", + ] + }], + "manifest_path": "PlayServicesResolver/Editor", + # Use README.md as documentation. + "documentation": "PlayServicesResolver/Editor/README.md", + "common_manifest": { + "name": "com.google.play-services-resolver", + }, + "export_upm": 1 + }] + }, set(), "1.0.0") + package = project.packages_by_name["play-services-resolver.unitypackage"] + + upm_package = package.write_upm( + export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), { + "1.0.0": { + "PlayServicesResolver/Editor/README.md": + "baa27a4c0385454899a759d9852966b7", + "PlayServicesResolver/Editor/" + "play-services-resolver_version-1.0.0_manifest.txt": + "353f6aace2cd42adb1343fc6a808f62e", + "com.google.play-services-resolver/package.json": + "782a38c5f19e4bb99e927976c8daa9ac", + "com.google.play-services-resolver/PlayServicesResolver": + "fa7daf703ad1430dad0cd8b764e5e6d2", + "com.google.play-services-resolver/PlayServicesResolver/" + "Editor": + "2334cd7684164851a8a53db5bd5923ca", + } + }, "1.0.0"), [self.assets_dir], self.staging_dir, 0) + + with tarfile.open(upm_package, "r:gz") as upm_package_file: + # Check included files. + self.assertCountEqual([ + "package", + "package/package.json", + "package/package.json.meta", + "package/PlayServicesResolver", + "package/PlayServicesResolver.meta", + "package/PlayServicesResolver/Editor", + "package/PlayServicesResolver/Editor.meta", + "package/PlayServicesResolver/Editor/Google.VersionHandler.dll", + "package/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta", + "package/PlayServicesResolver/Editor/" + "play-services-resolver_version-1.0.0_manifest.txt", + "package/PlayServicesResolver/Editor/" + "play-services-resolver_version-1.0.0_manifest.txt.meta", + "package/Documentation~", + "package/Documentation~/index.md", + ], upm_package_file.getnames()) + upm_package_file.extractall(self.staging_dir) + + self.assertTrue( + filecmp.cmp( + os.path.join( + self.assets_dir, + "PlayServicesResolver/Editor/README.md"), + os.path.join( + self.staging_dir, "package/Documentation~/index.md"))) + + def test_package_write_upm_missing_readme(self): + """Test write_upm() with misconfigured readme path.""" + project = export_unity_package.ProjectConfiguration( + { + "packages": [{ + "name": "play-services-resolver.unitypackage", + "imports": [{ + "paths": [ + "PlayServicesResolver/Editor/Google.VersionHandler.dll", + ] + }], + "manifest_path": "PlayServicesResolver/Editor", + "readme": "a/nonexist/path/README.md", + "common_manifest": { + "name": "com.google.play-services-resolver", + }, + "export_upm": 1 + }] + }, set(), "1.0.0") + package = project.packages_by_name["play-services-resolver.unitypackage"] + + with self.assertRaises(export_unity_package.ProjectConfigurationError): + package.write_upm( + export_unity_package.GuidDatabase( + export_unity_package.DuplicateGuidsChecker(), { + "1.0.0": { + "PlayServicesResolver/Editor/README.md": + "baa27a4c0385454899a759d9852966b7", + "PlayServicesResolver/Editor/" + "play-services-resolver_version-1.0.0_manifest.txt": + "353f6aace2cd42adb1343fc6a808f62e", + "com.google.play-services-resolver/package.json": + "782a38c5f19e4bb99e927976c8daa9ac", + "com.google.play-services-resolver/PlayServicesResolver": + "fa7daf703ad1430dad0cd8b764e5e6d2", + "com.google.play-services-resolver/PlayServicesResolver/" + "Editor": + "2334cd7684164851a8a53db5bd5923ca", + } + }, "1.0.0"), [self.assets_dir], self.staging_dir, 0) + + +class TestVersionHandler(absltest.TestCase): + """Test methods that generate Version Handler labels and filenames.""" + + def test_version_handler_tag(self): + """Generate a label or filename field for the Version Handler.""" + self.assertEqual( + "gvh_manifest", + export_unity_package.version_handler_tag( + islabel=True, + field=export_unity_package.VERSION_HANDLER_MANIFEST_FIELD_PREFIX, + value=None)) + self.assertEqual( + "manifest", + export_unity_package.version_handler_tag( + islabel=False, + field=export_unity_package.VERSION_HANDLER_MANIFEST_FIELD_PREFIX, + value=None)) + self.assertEqual( + "gvh_version-1.2.3-beta2", + export_unity_package.version_handler_tag( + islabel=True, + field=export_unity_package.VERSION_HANDLER_VERSION_FIELD_PREFIX, + value="1.2.3_beta2")) + self.assertEqual( + "version-1.2.3-beta2", + export_unity_package.version_handler_tag( + islabel=False, + field=export_unity_package.VERSION_HANDLER_VERSION_FIELD_PREFIX, + value="1.2.3_beta2")) + self.assertEqual( + "", export_unity_package.version_handler_tag( + islabel=False, field=None, value=None)) + + def test_version_handler_filename(self): + """Generate a filename for the Version Handler.""" + self.assertEqual( + "a/b/c/myplugin_version-1.2.3_manifest.txt", + export_unity_package.version_handler_filename( + "a/b/c/myplugin.txt", + [(export_unity_package.VERSION_HANDLER_VERSION_FIELD_PREFIX, + "1.2.3"), + (export_unity_package.VERSION_HANDLER_MANIFEST_FIELD_PREFIX, + None)])) + + +class FileOperationsTest(absltest.TestCase): + """Test file utility methods.""" + + def setUp(self): + """Unpack resources to a temporary directory.""" + super(FileOperationsTest, self).setUp() + self.assets_dir = os.path.join(TEST_DATA_PATH, "Assets") + self.temp_dir = os.path.join(FLAGS.test_tmpdir, "copy_temp") + self.expected_mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR + if platform.system() == 'Windows': + # Windows doesn't support the executable mode so ignore it in tests. + self.expected_mode = self.expected_mode & ~stat.S_IXUSR + os.makedirs(self.temp_dir) + + def tearDown(self): + """Clean up the temporary directory.""" + super(FileOperationsTest, self).tearDown() + shutil.rmtree(self.temp_dir) + + def test_copy_and_set_rwx(self): + """Copy a file and set it to readable / writeable and executable.""" + source_path = os.path.join( + self.assets_dir, + "PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt") + target_path = os.path.join(self.temp_dir, + "play-services-resolver_v1.2.87.0.txt") + self.assertFalse(os.path.exists(target_path)) + + export_unity_package.copy_and_set_rwx(source_path, target_path) + self.assertTrue(os.path.exists(target_path)) + self.assertTrue(filecmp.cmp(source_path, target_path)) + self.assertEqual(self.expected_mode, + os.stat(target_path).st_mode & stat.S_IRWXU) + + def test_copy_and_set_rwx_new_dir(self): + """Copy a file into a non-existent directory.""" + source_path = os.path.join( + self.assets_dir, + "PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt") + target_path = os.path.join( + self.temp_dir, + "a/nonexistent/directory/play-services-resolver_v1.2.87.0.txt") + self.assertFalse(os.path.exists(target_path)) + + export_unity_package.copy_and_set_rwx(source_path, target_path) + self.assertTrue(os.path.exists(target_path)) + self.assertTrue(filecmp.cmp(source_path, target_path)) + self.assertEqual(self.expected_mode, + os.stat(target_path).st_mode & stat.S_IRWXU) + + def test_copy_files_to_dir(self): + """Test copying files into a directory using a variety of target paths.""" + original_copy_and_set_rwx = export_unity_package.copy_and_set_rwx + try: + copied_files = [] + export_unity_package.copy_and_set_rwx = ( + lambda source, target: copied_files.append((source, target))) + self.assertEqual( + ["an/output/dir/something/to/copy.txt", + "an/output/dir/a/target/path.txt", + "an/output/dir/some/root/path.txt"], + export_unity_package.copy_files_to_dir( + ["something/to/copy.txt", + "something/else/to_copy.txt:a/target/path.txt", + "yet/another/file.txt:/some/root/path.txt"], + "an/output/dir")) + self.assertEqual( + [("something/to/copy.txt", "an/output/dir/something/to/copy.txt"), + ("something/else/to_copy.txt", "an/output/dir/a/target/path.txt"), + ("yet/another/file.txt", "an/output/dir/some/root/path.txt")], + copied_files) + finally: + export_unity_package.copy_and_set_rwx = original_copy_and_set_rwx + + def test_copy_dir_to_dir(self): + """Test copying directory into a directory recursively.""" + source_path = os.path.join( + self.assets_dir, + "PlayServicesResolver") + target_path = os.path.join( + FLAGS.test_tmpdir, + "a/nonexistent/directory") + self.assertFalse(os.path.exists(target_path)) + + export_unity_package.copy_and_set_rwx(source_path, target_path) + self.assertTrue(os.path.exists(target_path)) + cmp_result = filecmp.dircmp(source_path, target_path) + self.assertFalse(cmp_result.left_only or cmp_result.right_only or + cmp_result.diff_files) + # NOTE: Folders have the executable bit set on Windows. + self.assertEqual( + stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR, + os.stat(os.path.join(target_path, "Editor")).st_mode & stat.S_IRWXU) + self.assertEqual( + self.expected_mode, + (os.stat(os.path.join(target_path, "Editor.meta")).st_mode & + stat.S_IRWXU)) + + def test_find_in_dirs(self): + """Test find_in_dirs.""" + self.assertEqual( + export_unity_package.find_in_dirs( + "PlayServicesResolver", [self.assets_dir]), + os.path.join(self.assets_dir, "PlayServicesResolver")) + self.assertEqual( + export_unity_package.find_in_dirs( + "PlayServicesResolver/Editor.meta", [self.assets_dir]), + os.path.join(self.assets_dir, "PlayServicesResolver/Editor.meta")) + self.assertEqual( + export_unity_package.find_in_dirs("PlayServicesResolver", []), None) + self.assertEqual( + export_unity_package.find_in_dirs( + "a/nonexisting/file", [self.assets_dir]), + None) + +class ReadJsonFileTest(absltest.TestCase): + """Test reading a JSON file.""" + + def setUp(self): + """Create a temporary directory.""" + super(ReadJsonFileTest, self).setUp() + self.temp_dir = os.path.join(FLAGS.test_tmpdir, "json_temp") + os.makedirs(self.temp_dir) + + def tearDown(self): + """Clean up the temporary directory.""" + super(ReadJsonFileTest, self).tearDown() + shutil.rmtree(self.temp_dir) + + def test_read_json_file_into_ordered_dict(self): + """Read JSON into an OrderedDict.""" + json_filename = os.path.join(self.temp_dir, "test.json") + with open(json_filename, "wt") as json_file: + json_file.write("{\n" + " \"this\": \"is\",\n" + " \"just\": \"a\",\n" + " \"test\": \"of\",\n" + " \"ordered\": \"json\",\n" + " \"parsing\": 1\n" + "}\n") + dictionary = export_unity_package.read_json_file_into_ordered_dict( + json_filename) + self.assertEqual( + collections.OrderedDict([("this", "is"), + ("just", "a"), + ("test", "of"), + ("ordered", "json"), + ("parsing", 1)]), dictionary) + + def test_read_json_file_into_ordered_dict_missing_file(self): + """Try to read a non-existent JSON file.""" + with self.assertRaises(IOError) as context: + export_unity_package.read_json_file_into_ordered_dict( + os.path.join(self.temp_dir, "missing.json")) + self.assertRegexMatch(str(context.exception), + [r".*missing\.json"]) + + def test_read_json_file_into_ordered_dict_malformed_json(self): + """Try to read invalid JSON.""" + json_filename = os.path.join(self.temp_dir, "test.json") + with open(json_filename, "wt") as json_file: + json_file.write("{\n" + " \"json\": \"dislikes\",\n" + " \"trailing\": \"commas\",\n" + "}\n") + with self.assertRaises(ValueError) as context: + export_unity_package.read_json_file_into_ordered_dict( + json_filename) + self.assertRegexMatch(str(context.exception), + [r".*test\.json"]) + + +if __name__ == "__main__": + absltest.main() diff --git a/source/ExportUnityPackage/gen_guids.py b/source/ExportUnityPackage/gen_guids.py new file mode 100755 index 00000000..77e322f9 --- /dev/null +++ b/source/ExportUnityPackage/gen_guids.py @@ -0,0 +1,258 @@ +#!/usr/bin/python +# +# Copyright 2016 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +r"""This script generates stable guids for Unity asset paths. + +Since the command for this is typically generated from a blaze script, the paths +passed in are typically relative to the root google3 dir. So we'll use the +fact that this script is in google3 somewhere to find the root and fix the +paths to be fully qualified. + +The json file is expected to already exist as a precaution to avoid accidentally +creating a different one in the wrong location. The guids json file has a simple +format: +{ + "": { + "": "", + ... + }, + ... +} + +Example usage (output from blaze): + +python firebase/app/client/unity/gen_guids.py \ + --guids_file="firebase/app/client/unity/unity_packer/guids.json" \ + --version="1.0.0" \ + "Firebase/Plugins/Firebase.Database.dll" \ + "Firebase/Plugins/Firebase.Database.Unity.dll" + +""" + +import json +import os +import re +import uuid + +from absl import app +from absl import flags +from absl import logging +import distutils.version + +FLAGS = flags.FLAGS + +flags.DEFINE_string("guids_file", None, + "Path to the guids.json file to modify.") +flags.DEFINE_string("version", "1.0.0", + "The plugin version the GUID will be created for.") +flags.DEFINE_boolean("generate_new_guids", False, + "Whether to generate new GUIDs for command line " + "specified files (arguments following the flags). " + "If this is disabled, GUIDs will only be generated for " + "files that are not referenced by any package version.") + +GUID_REGEX = re.compile(r"^[0-9a-fA-F]{32}$") + + +def validate_guid_data(guid_data): + """Validate the specified matches the expected format for plugin asset GUIDs. + + Args: + guid_data: Dictionary in the following form to validate... + { + "": { + "": "", + ... + }, + ... + } + where is a plugin version, is the path of the asset + within a plugin and is the GUID assigned to the asset. + + Raises: + ValueError: If the dictionary doesn't match the expected format. + """ + # Validate the dictionary. + for version, guids_by_asset_paths in guid_data.items(): + if not isinstance(guids_by_asset_paths, dict): + raise ValueError("Version %s contains invalid GUID object %s" % + (version, guids_by_asset_paths)) + # version can be anything that can be converted to a string. + for asset_path, guid in guids_by_asset_paths.items(): + if not GUID_REGEX.match(guid): + raise ValueError("Version %s, asset path %s references invalid " + "GUID %s" % (version, asset_path, guid)) + + +def read_guid_data(filename): + """Read GUIDs from JSON file. + + Args: + filename: File to read asset GUIDs from. + + Returns: + Dictionary in the form expected by validate_guid_data(). + + Raises: + ValueError: If the JSON file doesn't match the expected format. + """ + with open(filename, "r") as guids_file: + guid_data = json.load(guids_file) + validate_guid_data(guid_data) + return guid_data + + +def remove_duplicate_guids(guid_data): + """Remove duplicate GUIDs that are present for prior versions of a plugin. + + Args: + guid_data: Dictionary in the form expected by validate_guid_data(). + This dictionary is modified in-place. + """ + # Compress map by removing duplicate GUIDs. + sorted_versions = sorted(guid_data, key=distutils.version.LooseVersion) + for version_index, version in enumerate(sorted_versions): + current_guids_by_filename = guid_data[version] + for filename in list(current_guids_by_filename.keys()): + current_guid = current_guids_by_filename[filename] + # Iterate through all versions prior to the current version. + for previous_version_index in range(version_index - 1, 0, -1): + # If the previous version contains the current GUID, remove it from the + # current map. + previous_guids_by_filename = ( + guid_data[sorted_versions[previous_version_index]]) + if previous_guids_by_filename.get(filename) == current_guid: + del current_guids_by_filename[filename] + break + + +def get_guids_by_asset_paths_for_version(guid_data, version): + """Get asset GUIDs by path for a plugin version. + + Args: + guid_data: Data to query for asset GUIDs. This should be a dictionary in + the form expected by validate_guid_data(). + version: Version dictionary to find in guid_data. + + Returns: + Dictionary that is referenced by the "version" section of the guid_data + dictionary. The returned value can be mutated to extend the guid_data + object. + """ + guids_by_asset_paths = guid_data.get(version, {}) + guid_data[version] = guids_by_asset_paths + return guids_by_asset_paths + + +def get_all_asset_paths(guid_data, version): + """Get all asset paths and their newest associated GUIDs. + + Args: + guid_data: Dictionary in the form expected by validate_guid_data(). + version: Maximum version to search for assets. + + Returns: + Dictionary of asset path to GUID. + """ + all_guids_by_asset_paths = {} + # Aggregate guids for older versions of files. + max_version = distutils.version.LooseVersion(version) + for current_version in sorted(guid_data, key=distutils.version.LooseVersion, + reverse=True): + # Skip all versions after the current version. + if distutils.version.LooseVersion(current_version) > max_version: + continue + # Add all guids for files to the current version. + guids_by_asset_paths = guid_data[current_version] + for asset_path, guid in guids_by_asset_paths.items(): + if asset_path not in all_guids_by_asset_paths: + all_guids_by_asset_paths[asset_path] = guid + return all_guids_by_asset_paths + + +def generate_guids_for_asset_paths(guid_data, version, asset_paths, + generate_new_guids): + """Generate GUIDs for a set of asset paths. + + Args: + guid_data: Dictionary in the form expected by validate_guid_data() to insert + the GUIDs into. + version: Plugin version the asset paths were introduced. + asset_paths: Asset paths to generate GUIDs for. These paths should be the + location of the assets when imported in a Unity plugin. + generate_new_guids: Whether to generate new GUIDs for files that have GUIDs + in prior versions of the plugin. + """ + all_guids_by_asset_paths = get_all_asset_paths(guid_data, version) + guids_by_asset_paths = get_guids_by_asset_paths_for_version(guid_data, + version) + for asset_path in set(asset_paths): + new_guid = uuid.uuid4().hex + if generate_new_guids: + guids_by_asset_paths[asset_path] = new_guid + elif asset_path not in all_guids_by_asset_paths: + guids_by_asset_paths[asset_path] = ( + guids_by_asset_paths.get(asset_path, new_guid)) + + +def write_guid_data(filename, guid_data): + """Write a GUIDs JSON file. + + Args: + filename: File to write data to. + guid_data: Dictionary in the form expected by validate_guid_data() to write + to the file. + """ + output = json.dumps(guid_data, indent=4, sort_keys=True) + with open(filename, "wt") as guids_file: + for line in output.splitlines(): + guids_file.write(line.rstrip() + "\n") + + +def main(argv_paths): + """Generates stable guids for Unity package assets. + + Since the command for this is typically generated from a blaze rule, the paths + passed in are typically relative to the root google3 dir. So we'll use the + fact that this script is in google3 somewhere to find the root and fix the + paths to be fully qualified. + + Args: + argv_paths: List of google3 relative paths to generate new guids for. The + relative script path is included in index 0, and is ignored. + + Returns: + The exit code status; 1 for error, 0 for success. + """ + # if it's not a cwd relative path, check if it's a google3 relative path. + guids_file_path = FLAGS.guids_file + if not os.path.exists(guids_file_path): + logging.error("Could not find the guids file (%s) to modify. If you wish " + "to start a new guids file at this path, please create the " + "empty file first.", FLAGS.guids_file) + return 1 + + guid_data = read_guid_data(guids_file_path) + remove_duplicate_guids(guid_data) + generate_guids_for_asset_paths(guid_data, FLAGS.version, set(argv_paths[1:]), + FLAGS.generate_new_guids) + write_guid_data(guids_file_path, guid_data) + return 0 + + +if __name__ == "__main__": + flags.mark_flag_as_required("guids_file") + app.run(main) diff --git a/source/ExportUnityPackage/gen_guids_test.py b/source/ExportUnityPackage/gen_guids_test.py new file mode 100755 index 00000000..c098b936 --- /dev/null +++ b/source/ExportUnityPackage/gen_guids_test.py @@ -0,0 +1,168 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for gen_guids.py script.""" + +import copy +import json +import os +import shutil +import sys + +from absl import flags +from absl.testing import absltest + +# pylint: disable=C6204 +# pylint: disable=W0403 +sys.path.append(os.path.dirname(__file__)) +import gen_guids +# pylint: enable=C6204 +# pylint: enable=W0403 + +FLAGS = flags.FLAGS + + +TEST_JSON = """\ +{ + "0.0.1": { + "pea/nut": "7311924048bd457bac6d713576c952da" + }, + "1.2.3": { + "foo/bar": "ba9f9118207d46248936105077947525", + "bish/bosh": "a308bff8c54a4c8d987dad79e96ed5e1" + }, + "2.3.4": { + "foo/bar": "ae88c0972b7448b5b36def1716f1d711", + "a/new/file": "816270c2a2a348e59cb9b7b096a24f50" + } +} +""" + +# Dictionary representation of TEST_JSON +TEST_DICT = { + "0.0.1": { + "pea/nut": "7311924048bd457bac6d713576c952da", + }, + "1.2.3": { + "foo/bar": "ba9f9118207d46248936105077947525", + "bish/bosh": "a308bff8c54a4c8d987dad79e96ed5e1" + }, + "2.3.4": { + "foo/bar": "ae88c0972b7448b5b36def1716f1d711", + "a/new/file": "816270c2a2a348e59cb9b7b096a24f50" + } +} + + +def delete_temporary_directory_contents(): + """Delete the contents of the temporary directory.""" + # If the temporary directory is populated, delete everything in there. + directory = FLAGS.test_tmpdir + for path in os.listdir(directory): + full_path = os.path.join(directory, path) + if os.path.isdir(full_path): + shutil.rmtree(full_path) + else: + os.unlink(full_path) + + +class GenGuidsTest(absltest.TestCase): + """Test the gen_guids module.""" + + def tearDown(self): + """Clean up the temporary directory.""" + delete_temporary_directory_contents() + super(GenGuidsTest, self).tearDown() + + def test_validate_guid_data(self): + """Ensure validate_guid_data() catches invalid JSON data.""" + gen_guids.validate_guid_data({}) + gen_guids.validate_guid_data({"1.2.3": {}}) + gen_guids.validate_guid_data( + {"1.2.3": {"foo/bar": "0123456789abcdef0123456789abcdef"}}) + with self.assertRaises(ValueError): + gen_guids.validate_guid_data({"1.2.3": {"foo/bar": "notaguid"}}) + + def test_read_guid_data(self): + """Read GUIDs from a JSON file.""" + guids_filename = os.path.join(FLAGS.test_tmpdir, "guids.json") + with open(guids_filename, "w") as guids_file: + guids_file.write(TEST_JSON) + guids_data = gen_guids.read_guid_data(guids_filename) + self.assertDictEqual(TEST_DICT, guids_data) + + def test_write_guid_data(self): + """Write GUIDs to a JSON file.""" + guids_filename = os.path.join(FLAGS.test_tmpdir, "guids.json") + gen_guids.write_guid_data(guids_filename, TEST_DICT) + with open(guids_filename, "rt") as guids_file: + guids_json = guids_file.read() + self.assertDictEqual(TEST_DICT, json.loads(guids_json)) + + def test_remove_duplicate_guids(self): + """Ensure duplicate GUIDs are removed from a dictionary of asset GUIDs.""" + duplicate_data = copy.deepcopy(TEST_DICT) + duplicate_data["2.3.4"]["bish/bosh"] = "a308bff8c54a4c8d987dad79e96ed5e1" + gen_guids.remove_duplicate_guids(duplicate_data) + self.assertDictEqual(TEST_DICT, duplicate_data) + + def test_get_guids_by_asset_paths_for_version(self): + """Get GUIDs for each asset path for a specific version.""" + guids_by_asset_paths = gen_guids.get_guids_by_asset_paths_for_version( + copy.deepcopy(TEST_DICT), "1.2.3") + self.assertDictEqual({ + "foo/bar": "ba9f9118207d46248936105077947525", + "bish/bosh": "a308bff8c54a4c8d987dad79e96ed5e1" + }, guids_by_asset_paths) + self.assertDictEqual({}, gen_guids.get_guids_by_asset_paths_for_version( + copy.deepcopy(TEST_DICT), "100.0.0")) + + def test_get_all_asset_paths(self): + """Get GUIDs for all assets up to the specified version.""" + guids_by_asset_paths = gen_guids.get_all_asset_paths(TEST_DICT, "1.2.3") + self.assertDictEqual({ + "pea/nut": "7311924048bd457bac6d713576c952da", + "foo/bar": "ba9f9118207d46248936105077947525", + "bish/bosh": "a308bff8c54a4c8d987dad79e96ed5e1" + }, guids_by_asset_paths) + guids_by_asset_paths = gen_guids.get_all_asset_paths(TEST_DICT, "2.3.4") + self.assertDictEqual({ + "pea/nut": "7311924048bd457bac6d713576c952da", + "foo/bar": "ae88c0972b7448b5b36def1716f1d711", + "bish/bosh": "a308bff8c54a4c8d987dad79e96ed5e1", + "a/new/file": "816270c2a2a348e59cb9b7b096a24f50" + }, guids_by_asset_paths) + + def test_generate_guids_for_asset_paths(self): + """Generate GUIDs for a set of asset paths.""" + guid_data = copy.deepcopy(TEST_DICT) + gen_guids.generate_guids_for_asset_paths( + guid_data, "2.3.4", ("pea/nut", "another/file", "more/things"), False) + self.assertEqual(TEST_DICT["0.0.1"]["pea/nut"], + guid_data["0.0.1"].get("pea/nut")) + self.assertNotEmpty(guid_data["2.3.4"].get("another/file")) + self.assertNotEmpty(guid_data["2.3.4"].get("more/things")) + + guid_data = copy.deepcopy(TEST_DICT) + gen_guids.generate_guids_for_asset_paths( + guid_data, "2.3.4", ("pea/nut", "another/file", "more/things"), True) + self.assertNotEqual(TEST_DICT["0.0.1"]["pea/nut"], + guid_data["2.3.4"].get("pea/nut")) + self.assertNotEmpty(guid_data["2.3.4"].get("pea/nut")) + self.assertNotEmpty(guid_data["2.3.4"].get("another/file")) + self.assertNotEmpty(guid_data["2.3.4"].get("more/things")) + + +if __name__ == "__main__": + absltest.main() diff --git a/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.Analytics.dll b/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.Analytics.dll new file mode 100644 index 00000000..0399d610 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.Analytics.dll @@ -0,0 +1 @@ +Firebase.Analytics.dll diff --git a/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.App.dll b/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.App.dll new file mode 100644 index 00000000..0ccb132d --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.App.dll @@ -0,0 +1 @@ +Firebase.App.dll diff --git a/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.Auth.dll b/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.Auth.dll new file mode 100644 index 00000000..26d4f1c7 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/Firebase/Plugins/Firebase.Auth.dll @@ -0,0 +1 @@ +Firebase.Auth.dll diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Doc/index.md b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Doc/index.md new file mode 100644 index 00000000..e89fa552 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Doc/index.md @@ -0,0 +1 @@ +A documentation file diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.111.0.dll.mdb.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor.meta similarity index 52% rename from exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.111.0.dll.mdb.meta rename to source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor.meta index 9e6d261e..457843c6 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.111.0.dll.mdb.meta +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor.meta @@ -1,9 +1,7 @@ fileFormatVersion: 2 -guid: 9bfab54114734b7884fe507e7a19b45f -labels: -- gvh_version-1.2.111.0 -- gvh -timeCreated: 1538009133 +guid: e105e00cdce8456482d26b1fcd1ca47d +folderAsset: yes +timeCreated: 1448926516 licenseType: Pro DefaultImporter: userData: diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/CHANGELOG.md b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/CHANGELOG.md new file mode 100644 index 00000000..5568b66b --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/CHANGELOG.md @@ -0,0 +1 @@ +A changelog file diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll new file mode 100644 index 00000000..15e7ecd0 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll @@ -0,0 +1 @@ +Google.IOSResolver_v1.2.87.0.dll diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.111.0.dll.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll.meta similarity index 86% rename from exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.111.0.dll.meta rename to source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll.meta index 2a364309..367d2e9e 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.111.0.dll.meta +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll.meta @@ -1,9 +1,9 @@ fileFormatVersion: 2 -guid: c646678e3a6a4df5969a38392783d303 +guid: 5889e1be319e46899d75db5bdf372fb9 labels: -- gvh_version-1.2.111.0 +- gvh_v1.2.87.0 - gvh -- gvh_targets-editor +- gvh_teditor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll new file mode 100644 index 00000000..99e97b7c --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll @@ -0,0 +1 @@ +Google.JarResolver_v1.2.87.0.dll diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll.meta similarity index 86% rename from plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.meta rename to source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll.meta index f51e9178..9a357d5b 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.meta +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll.meta @@ -1,9 +1,9 @@ fileFormatVersion: 2 -guid: c16d9c05cc6d4e32be3b86440c709558 +guid: 380e8ddf4f4a46bfa31759d4ad1e82bc labels: -- gvh_version-1.2.111.0 +- gvh_v1.2.87.0 - gvh -- gvh_targets-editor +- gvh_teditor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll new file mode 100644 index 00000000..0eee039d --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll @@ -0,0 +1 @@ +Google.VersionHandler.dll diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta similarity index 86% rename from plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta rename to source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta index 71ee4fe2..43e26f6a 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta @@ -1,9 +1,9 @@ fileFormatVersion: 2 -guid: 16888424fc064e5298648415a844a879 +guid: 06f6f385a4ad409884857500a3c04441 labels: -- gvh_version-1.2.111.0 +- gvh_v1.2.86.0 - gvh -- gvh_targets-editor +- gvh_teditor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll new file mode 100644 index 00000000..3afddb47 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll @@ -0,0 +1 @@ +Google.VersionHandlerImpl_v1.2.87.0.dll diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.111.0.dll.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll.meta similarity index 86% rename from exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.111.0.dll.meta rename to source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll.meta index 757abd74..fc4128d4 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.111.0.dll.meta +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll.meta @@ -1,9 +1,9 @@ fileFormatVersion: 2 -guid: fb8efebd770744099a4896ab6818de16 +guid: dc0556b86e9e4751a1553508cd89142f labels: -- gvh_version-1.2.111.0 +- gvh_v1.2.87.0 - gvh -- gvh_targets-editor +- gvh_teditor PluginImporter: externalObjects: {} serializedVersion: 2 diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/LICENSE b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/LICENSE new file mode 100644 index 00000000..d0a85ab1 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/LICENSE @@ -0,0 +1 @@ +A license file diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/README.md b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/README.md new file mode 100644 index 00000000..378dddb9 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/README.md @@ -0,0 +1 @@ +A readme file diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt new file mode 100644 index 00000000..c3495665 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt @@ -0,0 +1,4 @@ +Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll +Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll +Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll +Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll diff --git a/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt.meta new file mode 100644 index 00000000..4b034385 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4a1d3acb6c7545ffbd0b6601e3f57aee +labels: +- gvh_v1.2.87.0 +- gvh +- gvh_manifest +timeCreated: 1474401009 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/ExternalDependencyManager.sln b/source/ExternalDependencyManager.sln new file mode 100644 index 00000000..9dda1103 --- /dev/null +++ b/source/ExternalDependencyManager.sln @@ -0,0 +1,89 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JarResolverLib", "JarResolverLib\JarResolverLib.csproj", "{CC4F239D-3C7F-4164-830F-9215AE15B32A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JarResolverTests", "JarResolverTests\JarResolverTests.csproj", "{593254D7-6358-40A6-B0C8-F0616BBF499D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTester", "IntegrationTester\IntegrationTester.csproj", "{DBD8D4D9-61AC-4E75-8CDC-CABE7033A040}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AndroidResolver", "AndroidResolver\AndroidResolver.csproj", "{82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AndroidResolverIntegrationTests", "AndroidResolver\test\AndroidResolverIntegrationTests.csproj", "{6CB19B5A-371A-5E50-A3F7-E24F1696D501}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionHandler", "VersionHandler\VersionHandler.csproj", "{5378B37A-887E-49ED-A8AE-42FA843AA9DC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionHandlerImpl", "VersionHandlerImpl\VersionHandlerImpl.csproj", "{1E162334-8EA2-440A-9B3A-13FD8FE5C22E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionHandlerImplTests", "VersionHandlerImpl\unit_tests\VersionHandlerImplTests.csproj", "{9FD33878-5B6B-411D-A3C7-D2A5E7E63182}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOSResolver", "IOSResolver\IOSResolver.csproj", "{5B581BAE-D432-41AB-AEED-FD269AEA081D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageManagerResolver", "PackageManagerResolver\PackageManagerResolver.csproj", "{77EBE819-CBE6-4CA8-A791-ED747EA29D30}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageManagerResolverTests", "PackageManagerResolver\unit_tests\PackageManagerResolverTests.csproj", "{FEECDCE1-F528-4931-A4CF-808DDBCE1A8D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageManagerClientIntegrationTests", "PackageManagerResolver\test\PackageManagerClientIntegrationTests.csproj", "{00A0DB5E-1120-24C9-331A-BE692C1F7C01}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageMigratorIntegrationTests", "PackageManagerResolver\test\PackageMigratorIntegrationTests.csproj", "{4DBDEE33-4B6C-A866-93FE-04C15486BB03}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {593254D7-6358-40A6-B0C8-F0616BBF499D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {593254D7-6358-40A6-B0C8-F0616BBF499D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {593254D7-6358-40A6-B0C8-F0616BBF499D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {593254D7-6358-40A6-B0C8-F0616BBF499D}.Release|Any CPU.Build.0 = Release|Any CPU + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040}.Release|Any CPU.Build.0 = Release|Any CPU + {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Release|Any CPU.Build.0 = Release|Any CPU + {6CB19B5A-371A-5E50-A3F7-E24F1696D501}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6CB19B5A-371A-5E50-A3F7-E24F1696D501}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6CB19B5A-371A-5E50-A3F7-E24F1696D501}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6CB19B5A-371A-5E50-A3F7-E24F1696D501}.Release|Any CPU.Build.0 = Release|Any CPU + {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Release|Any CPU.Build.0 = Release|Any CPU + {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Release|Any CPU.Build.0 = Release|Any CPU + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Release|Any CPU.Build.0 = Release|Any CPU + {9FD33878-5B6B-411D-A3C7-D2A5E7E63182}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FD33878-5B6B-411D-A3C7-D2A5E7E63182}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FD33878-5B6B-411D-A3C7-D2A5E7E63182}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FD33878-5B6B-411D-A3C7-D2A5E7E63182}.Release|Any CPU.Build.0 = Release|Any CPU + {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Release|Any CPU.Build.0 = Release|Any CPU + {77EBE819-CBE6-4CA8-A791-ED747EA29D30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77EBE819-CBE6-4CA8-A791-ED747EA29D30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77EBE819-CBE6-4CA8-A791-ED747EA29D30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77EBE819-CBE6-4CA8-A791-ED747EA29D30}.Release|Any CPU.Build.0 = Release|Any CPU + {FEECDCE1-F528-4931-A4CF-808DDBCE1A8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FEECDCE1-F528-4931-A4CF-808DDBCE1A8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FEECDCE1-F528-4931-A4CF-808DDBCE1A8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FEECDCE1-F528-4931-A4CF-808DDBCE1A8D}.Release|Any CPU.Build.0 = Release|Any CPU + {00A0DB5E-1120-24C9-331A-BE692C1F7C01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00A0DB5E-1120-24C9-331A-BE692C1F7C01}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00A0DB5E-1120-24C9-331A-BE692C1F7C01}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00A0DB5E-1120-24C9-331A-BE692C1F7C01}.Release|Any CPU.Build.0 = Release|Any CPU + {4DBDEE33-4B6C-A866-93FE-04C15486BB03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4DBDEE33-4B6C-A866-93FE-04C15486BB03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4DBDEE33-4B6C-A866-93FE-04C15486BB03}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4DBDEE33-4B6C-A866-93FE-04C15486BB03}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/source/IOSResolver/IOSResolver.csproj b/source/IOSResolver/IOSResolver.csproj index a4a0864b..e16e2abf 100644 --- a/source/IOSResolver/IOSResolver.csproj +++ b/source/IOSResolver/IOSResolver.csproj @@ -51,7 +51,7 @@ - ..\PlayServicesResolver\bin\Release\Google.JarResolver.dll + ..\AndroidResolver\bin\Release\Google.JarResolver.dll ..\VersionHandler\bin\Release\Google.VersionHandler.dll @@ -72,9 +72,9 @@ - + {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A} - PlayServicesResolver + AndroidResolver {5378B37A-887E-49ED-A8AE-42FA843AA9DC} diff --git a/source/IOSResolver/src/IOSResolver.cs b/source/IOSResolver/src/IOSResolver.cs index 26f570a5..a272810e 100644 --- a/source/IOSResolver/src/IOSResolver.cs +++ b/source/IOSResolver/src/IOSResolver.cs @@ -72,12 +72,12 @@ private class Pod { /// Since order is important sources specified in this list /// are interleaved across each Pod added to the resolver. /// e.g Pod1.source[0], Pod2.source[0] ... - //// Pod1.source[1], Pod2.source[1] etc. + /// Pod1.source[1], Pod2.source[1] etc. /// /// See: https://guides.cocoapods.org/syntax/podfile.html#source /// public List sources = new List() { - "https://github.com/CocoaPods/Specs.git" + "https://cdn.cocoapods.org/" }; /// @@ -86,6 +86,11 @@ private class Pod { /// public string minTargetSdk = null; + /// + /// Whether to add this pod to all targets when multiple + /// + public bool addToAllTargets = false; + /// /// Tag that indicates where this was created. /// @@ -162,6 +167,8 @@ public string PodFilePodLine { /// bitcode. /// Minimum target SDK revision required by /// this pod. + /// Whether to add this pod to all targets when multiple + /// target is supported. /// List of sources to search for all pods. /// Each source is a URL that is injected in the source section of a Podfile /// See https://guides.cocoapods.org/syntax/podfile.html#source for the description of @@ -169,7 +176,8 @@ public string PodFilePodLine { /// Dictionary of additional properties for the pod /// reference. public Pod(string name, string version, bool bitcodeEnabled, string minTargetSdk, - IEnumerable sources, Dictionary propertiesByName) { + bool addToAllTargets, IEnumerable sources, + Dictionary propertiesByName) { this.name = name; this.version = version; if (propertiesByName != null) { @@ -177,6 +185,7 @@ public Pod(string name, string version, bool bitcodeEnabled, string minTargetSdk } this.bitcodeEnabled = bitcodeEnabled; this.minTargetSdk = minTargetSdk; + this.addToAllTargets = addToAllTargets; if (sources != null) { var allSources = new List(sources); allSources.AddRange(this.sources); @@ -186,9 +195,9 @@ public Pod(string name, string version, bool bitcodeEnabled, string minTargetSdk /// /// Convert min target SDK to an integer in the form - // (major * 10) + minor. + /// (major * 10) + minor. /// - /// Numeric minimum SDK revision required by this pod. + /// Numeric minimum SDK revision required by this pod. public int MinTargetSdkToVersion() { string sdkString = String.IsNullOrEmpty(minTargetSdk) ? "0.0" : minTargetSdk; @@ -305,6 +314,7 @@ protected override bool Read(string filename, Logger logger) { string versionSpec = null; bool bitcodeEnabled = true; string minTargetSdk = null; + bool addToAllTargets = false; var propertiesByName = new Dictionary(); if (!XmlUtilities.ParseXmlTextFileElements( filename, logger, @@ -328,10 +338,15 @@ protected override bool Read(string filename, Logger logger) { } versionSpec = reader.GetAttribute("version"); var bitcodeEnabledString = - (reader.GetAttribute("bitcode") ?? "").ToLower(); + (reader.GetAttribute("bitcodeEnabled") ?? "").ToLower(); bitcodeEnabled |= trueStrings.Contains(bitcodeEnabledString); bitcodeEnabled &= !falseStrings.Contains(bitcodeEnabledString); + var addToAllTargetsString = + (reader.GetAttribute("addToAllTargets") ?? "").ToLower(); + addToAllTargets |= trueStrings.Contains(addToAllTargetsString); + addToAllTargets &= !falseStrings.Contains(addToAllTargetsString); minTargetSdk = reader.GetAttribute("minTargetSdk"); + sources = new List(); if (podName == null) { logger.Log( @@ -344,6 +359,7 @@ protected override bool Read(string filename, Logger logger) { AddPodInternal(podName, preformattedVersion: versionSpec, bitcodeEnabled: bitcodeEnabled, minTargetSdk: minTargetSdk, + addToAllTargets: addToAllTargets, sources: sources, overwriteExistingPod: false, createdBy: String.Format("{0}:{1}", @@ -375,7 +391,9 @@ protected override bool Read(string filename, Logger logger) { } return true; } - return false; + // Ignore unknown tags so that different configurations can be stored in the + // same file. + return true; })) { return false; } @@ -388,12 +406,12 @@ protected override bool Read(string filename, Logger logger) { new SortedDictionary(); // Order of post processing operations. - private const int BUILD_ORDER_REFRESH_DEPENDENCIES = 1; - private const int BUILD_ORDER_CHECK_COCOAPODS_INSTALL = 2; - private const int BUILD_ORDER_PATCH_PROJECT = 3; - private const int BUILD_ORDER_GEN_PODFILE = 4; - private const int BUILD_ORDER_INSTALL_PODS = 5; - private const int BUILD_ORDER_UPDATE_DEPS = 6; + private const int BUILD_ORDER_REFRESH_DEPENDENCIES = 10; + private const int BUILD_ORDER_CHECK_COCOAPODS_INSTALL = 20; + private const int BUILD_ORDER_PATCH_PROJECT = 30; + private const int BUILD_ORDER_GEN_PODFILE = 40; + private const int BUILD_ORDER_INSTALL_PODS = 50; + private const int BUILD_ORDER_UPDATE_DEPS = 60; // This is appended to the Podfile filename to store a backup of the original Podfile. // ie. "Podfile_Unity". @@ -405,6 +423,21 @@ protected override bool Read(string filename, Logger logger) { " > sudo gem install -n /usr/local/bin cocoapods\n" + " > pod setup"); + // Dialog box text when the podfile version exceeds the currently configured + // target iOS or tvOS sdk version. + private const string TARGET_SDK_NEEDS_UPDATE_STRING = ( + "Target SDK selected in the {0} Player Settings ({1}) is not supported by " + + "the Cocoapods included in this project. The build will very " + + "likely fail. The minimum supported version is \"{2}\" required " + + "by pods ({3})\n" + + "Would you like to update the target SDK version?"); + + // Dialog box text when the IOSResolver has updated the target sdk version on behalf + // of the user. + private const string UPDATED_TARGET_SDK_STRING = ( + "Target {0} SDK has been updated from {1} to {2}. " + + "You must restart the build for this change to take effect."); + // Pod executable filename. private static string POD_EXECUTABLE = "pod"; // Default paths to search for the "pod" command before falling back to @@ -412,6 +445,7 @@ protected override bool Read(string filename, Logger logger) { private static string[] POD_SEARCH_PATHS = new string[] { "/usr/local/bin", "/usr/bin", + "/opt/homebrew/bin", }; // Ruby Gem executable filename. private static string GEM_EXECUTABLE = "gem"; @@ -422,8 +456,10 @@ protected override bool Read(string filename, Logger logger) { public const string PROJECT_NAME = "Unity-iPhone"; /// - /// Main executable target of the Xcode project generated by Unity. + /// Main executable target of the Xcode project generated by Unity before they added support + /// for using Unity as a library or framework. This will be null if it is not supported. /// + /// This is deprecated, use XcodeTargetNames or GetXcodeTargetGuids instead. public static string TARGET_NAME = null; // Keys in the editor preferences which control the behavior of this module. @@ -442,6 +478,9 @@ protected override bool Read(string filename, Logger logger) { // Whether execution of the pod tool is performed via the shell. private const string PREFERENCE_POD_TOOL_EXECUTION_VIA_SHELL_ENABLED = PREFERENCE_NAMESPACE + "PodToolExecutionViaShellEnabled"; + // Whether environment variables should be set when execution is performed via the shell. + private const string PREFERENCE_POD_TOOL_SHELL_EXECUTION_SET_LANG = + PREFERENCE_NAMESPACE + "PodToolShellExecutionSetLang"; // Whether to try to install Cocoapods tools when iOS is selected as the target platform. private const string PREFERENCE_AUTO_POD_TOOL_INSTALL_IN_EDITOR = PREFERENCE_NAMESPACE + "AutoPodToolInstallInEditor"; @@ -451,6 +490,26 @@ protected override bool Read(string filename, Logger logger) { // Whether to skip pod install when using workspace integration. private const string PREFERENCE_SKIP_POD_INSTALL_WHEN_USING_WORKSPACE_INTEGRATION = PREFERENCE_NAMESPACE + "SkipPodInstallWhenUsingWorkspaceIntegration"; + // Whether to add "use_frameworks!" in the Podfile. + private const string PREFERENCE_PODFILE_ADD_USE_FRAMEWORKS = + PREFERENCE_NAMESPACE + "PodfileAddUseFrameworks"; + // Whether to statically link framework in the Podfile. + private const string PREFERENCE_PODFILE_STATIC_LINK_FRAMEWORKS = + PREFERENCE_NAMESPACE + "PodfileStaticLinkFrameworks"; + // Whether to add Dummy.swift to the generated Xcode project as a workaround to support pods + // with Swift Framework. + private const string PREFERENCE_SWIFT_FRAMEWORK_SUPPORT_WORKAROUND = + PREFERENCE_NAMESPACE + "SwiftFrameworkSupportWorkaroundEnabled"; + // The Swift Language Version (SWIFT_VERSION) to update to the generated Xcode Project. + private const string PREFERENCE_SWIFT_LANGUAGE_VERSION = + PREFERENCE_NAMESPACE + "SwiftLanguageVersion"; + // Whether to add an main target to Podfile for Unity 2019.3+. + private const string PREFERENCE_PODFILE_ALWAYS_ADD_MAIN_TARGET = + PREFERENCE_NAMESPACE + "PodfileAlwaysAddMainTarget"; + // Whether to allow the same pods to be in multiple targets, if specified in Dependecies.xml. + private const string PREFERENCE_PODFILE_ALLOW_PODS_IN_MULTIPLE_TARGETS = + PREFERENCE_NAMESPACE + "PodfileAllowPodsInMultipleTargets"; + // List of preference keys, used to restore default settings. private static string[] PREFERENCE_KEYS = new [] { PREFERENCE_COCOAPODS_INSTALL_ENABLED, @@ -460,7 +519,13 @@ protected override bool Read(string filename, Logger logger) { PREFERENCE_POD_TOOL_EXECUTION_VIA_SHELL_ENABLED, PREFERENCE_AUTO_POD_TOOL_INSTALL_IN_EDITOR, PREFERENCE_WARN_UPGRADE_WORKSPACE, - PREFERENCE_SKIP_POD_INSTALL_WHEN_USING_WORKSPACE_INTEGRATION + PREFERENCE_SKIP_POD_INSTALL_WHEN_USING_WORKSPACE_INTEGRATION, + PREFERENCE_PODFILE_ADD_USE_FRAMEWORKS, + PREFERENCE_PODFILE_STATIC_LINK_FRAMEWORKS, + PREFERENCE_SWIFT_FRAMEWORK_SUPPORT_WORKAROUND, + PREFERENCE_SWIFT_LANGUAGE_VERSION, + PREFERENCE_PODFILE_ALWAYS_ADD_MAIN_TARGET, + PREFERENCE_PODFILE_ALLOW_PODS_IN_MULTIPLE_TARGETS }; // Whether the xcode extension was successfully loaded. @@ -490,8 +555,12 @@ protected override bool Read(string filename, Logger logger) { private static string PODFILE_GENERATED_COMMENT = "# IOSResolver Generated Podfile"; // Default iOS target SDK if the selected version is invalid. - private const int DEFAULT_TARGET_SDK = 82; - // Valid iOS target SDK version. + private const int DEFAULT_IOS_TARGET_SDK = 82; + + // Default tvOS target SDK if the selected version is invalid. + private const string DEFAULT_TVOS_TARGET_SDK = "12.0"; + + // Valid iOS / tvOS target SDK version. private static Regex TARGET_SDK_REGEX = new Regex("^[0-9]+\\.[0-9]$"); // Current window being used for a long running shell command. @@ -530,12 +599,27 @@ protected override bool Read(string filename, Logger logger) { // Parses a source URL from a Podfile. private static Regex PODFILE_SOURCE_REGEX = new Regex(@"^\s*source\s+'([^']*)'"); + // Parses comments from a Podfile + private static Regex PODFILE_COMMENT_REGEX = new Regex(@"^\s*\#"); + // Parses dependencies from XML dependency files. private static IOSXmlDependencies xmlDependencies = new IOSXmlDependencies(); // Project level settings for this module. private static ProjectSettings settings = new ProjectSettings(PREFERENCE_NAMESPACE); + /// + /// Polls for changes to target iOS SDK version. + /// + private static PlayServicesResolver.PropertyPoller iosTargetSdkPoller = + new PlayServicesResolver.PropertyPoller("iOS Target SDK"); + + /// + /// Polls for changes to target tvOS SDK version. + /// + private static PlayServicesResolver.PropertyPoller tvosTargetSdkPoller = + new PlayServicesResolver.PropertyPoller("tvOS Target SDK"); + // Search for a file up to a maximum search depth stopping the // depth first search each time the specified file is found. private static List FindFile( @@ -639,11 +723,36 @@ private static Assembly ResolveUnityEditoriOSXcodeExtension( /// Initialize the module. /// static IOSResolver() { + // Load log preferences. + UpdateLoggerLevel(VerboseLoggingEnabled); + // NOTE: We can't reference the UnityEditor.iOS.Xcode module in this // method as the Mono runtime in Unity 4 and below requires all // dependencies of a method are loaded before the method is executed // so we install the DLL loader first then try using the Xcode module. RemapXcodeExtension(); + + // Delay initialization until the build target is iOS and the editor is not in play + // mode. + EditorInitializer.InitializeOnMainThread(condition: () => { + return (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS || + EditorUserBuildSettings.activeBuildTarget == BuildTarget.tvOS) && + !EditorApplication.isPlayingOrWillChangePlaymode; + }, initializer: Initialize, name: "IOSResolver", logger: logger); + } + + /// + /// Initialize the module. This should be called on the main thread only if + /// current active build target is iOS and not in play mode. + /// + private static bool Initialize() { + if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.iOS && + EditorUserBuildSettings.activeBuildTarget != BuildTarget.tvOS) { + throw new Exception("IOSResolver.Initialize() is called when active build target " + + "is not iOS+. This should never happen. If it does, please report to the " + + "developer."); + } + // NOTE: It's not possible to catch exceptions a missing reference // to the UnityEditor.iOS.Xcode assembly in this method as the runtime // will attempt to load the assembly before the method is executed so @@ -651,68 +760,74 @@ static IOSResolver() { try { InitializeTargetName(); } catch (Exception exception) { - if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS) { - Log("Failed: " + exception.ToString(), level: LogLevel.Error); - if (exception is FileNotFoundException || - exception is TypeInitializationException || - exception is TargetInvocationException) { - // It's likely we failed to load the iOS Xcode extension. - Debug.LogWarning( - "Failed to load the " + - "UnityEditor.iOS.Extensions.Xcode dll. " + - "Is iOS support installed?"); - } else { - throw exception; - } + Log("Failed: " + exception.ToString(), level: LogLevel.Error); + if (exception is FileNotFoundException || + exception is TypeInitializationException || + exception is TargetInvocationException) { + // It's likely we failed to load the iOS Xcode extension. + Debug.LogWarning( + "Failed to load the " + + "UnityEditor.iOS.Extensions.Xcode dll. " + + "Is iOS support installed?"); + } else { + throw exception; } } // If Cocoapod tool auto-installation is enabled try installing on the first update of // the editor when the editor environment has been initialized. - if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS && - AutoPodToolInstallInEditorEnabled && CocoapodsIntegrationEnabled && + if (AutoPodToolInstallInEditorEnabled && CocoapodsIntegrationEnabled && !ExecutionEnvironment.InBatchMode) { - RunOnMainThread.Run(() => { AutoInstallCocoapods(); }, runNow: false); + AutoInstallCocoapods(); + } + // Install / remove target SDK property pollers. + SetEnablePollTargetSdks(PodfileGenerationEnabled); + // Load XML dependencies on the next editor update. + if (PodfileGenerationEnabled) { + RefreshXmlDependencies(); } - // Prompt the user to use workspaces if they aren't at least using project level // integration. - if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS && - (CocoapodsIntegrationMethod)settings.GetInt(PREFERENCE_COCOAPODS_INTEGRATION_METHOD, + if ((CocoapodsIntegrationMethod)settings.GetInt(PREFERENCE_COCOAPODS_INTEGRATION_METHOD, CocoapodsIntegrationUpgradeDefault) == CocoapodsIntegrationMethod.None && - !ExecutionEnvironment.InBatchMode && !UpgradeToWorkspaceWarningDisabled) { + !ExecutionEnvironment.InBatchMode && !UpgradeToWorkspaceWarningDisabled) { - switch (EditorUtility.DisplayDialogComplex( + DialogWindow.Display( "Warning: CocoaPods integration is disabled!", "Would you like to enable CocoaPods integration with workspaces?\n\n" + "Unity 5.6+ now supports loading workspaces generated from CocoaPods.\n" + "If you enable this, and still use Unity less than 5.6, it will fallback " + "to integrating CocoaPods with the .xcodeproj file.\n", - "Yes", "Not Now", "Silence Warning")) { - case 0: // Yes - settings.SetInt(PREFERENCE_COCOAPODS_INTEGRATION_METHOD, - (int)CocoapodsIntegrationMethod.Workspace); - break; - case 1: // Not now - break; - case 2: // Ignore - UpgradeToWorkspaceWarningDisabled = true; - break; - } + DialogWindow.Option.Selected0, "Yes", "Not Now", "Silence Warning", + complete: (selectedOption) => { + switch (selectedOption) { + case DialogWindow.Option.Selected0: // Yes + settings.SetInt(PREFERENCE_COCOAPODS_INTEGRATION_METHOD, + (int)CocoapodsIntegrationMethod.Workspace); + break; + case DialogWindow.Option.Selected1: // Not now + break; + case DialogWindow.Option.Selected2: // Ignore + UpgradeToWorkspaceWarningDisabled = true; + break; + } + }); } + + return true; } /// /// Link to the documentation. /// - [MenuItem("Assets/Play Services Resolver/iOS Resolver/Documentation")] + [MenuItem("Assets/External Dependency Manager/iOS Resolver/Documentation")] public static void OpenDocumentation() { - Application.OpenURL(VersionHandlerImpl.DocumentationUrl("#ios-resolver-usage")); + analytics.OpenUrl(VersionHandlerImpl.DocumentationUrl("#ios-resolver-usage"), "Usage"); } // Display the iOS resolver settings menu. - [MenuItem("Assets/Play Services Resolver/iOS Resolver/Settings")] + [MenuItem("Assets/External Dependency Manager/iOS Resolver/Settings")] public static void SettingsDialog() { IOSResolverSettingsDialog window = (IOSResolverSettingsDialog) EditorWindow.GetWindow(typeof(IOSResolverSettingsDialog), true, @@ -721,11 +836,81 @@ public static void SettingsDialog() { window.Show(); } + /// + /// Whether Unity only exports a single Xcode build target. This will be true if the current + /// version of Unity supports multiple Xcode build targets, for example if Unity supports use + /// as a library or framework. + /// + internal static bool MultipleXcodeTargetsSupported { + get { + try { + return MultipleXcodeTargetsSupportedInternal(); + } catch (Exception e) { + return false; + } + } + } + + private static bool MultipleXcodeTargetsSupportedInternal() { + return typeof(UnityEditor.iOS.Xcode.PBXProject).GetMethod( + "GetUnityMainTargetGuid", Type.EmptyTypes) != null; + } + + /// + /// Name of the Xcode main target generated by Unity. + /// + public static string XcodeMainTargetName { + get { + try { + return XcodeMainTargetNameInternal(); + } catch (Exception e) { + return "Unity-iPhone"; + } + } + } + + private static string XcodeMainTargetNameInternal() { + // NOTE: Unity-iPhone is hard coded in UnityEditor.iOS.Xcode.PBXProject and will no + // longer be exposed via GetUnityTargetName(). It hasn't changed in many years though + // so we'll use this constant as a relatively safe default. + return MultipleXcodeTargetsSupported ? "Unity-iPhone" : + (string)VersionHandler.InvokeStaticMethod(typeof(UnityEditor.iOS.Xcode.PBXProject), + "GetUnityTargetName", null); + } + + /// + /// Name of the Xcode UnityFramework target generated by Unity 2019.3+ + /// + public static string XcodeUnityFrameworkTargetName { + get { + return "UnityFramework"; + } + } + + /// + /// Name of the Xcode target which contains Unity libraries. + /// From Unity 2019.3+, Unity includes all its libraries and native libraries under Assets + /// folder to 'UnityFramework' instead of 'Unity-iPhone'. + /// + public static string XcodeTargetWithUnityLibraries { + get { + return MultipleXcodeTargetsSupported ? + XcodeUnityFrameworkTargetName : XcodeMainTargetName; + } + } + /// /// Initialize the TARGET_NAME property. + /// This will be "Unity-iPhone" in versions of Unity (2019.3+) that added support for using + /// Unity as a library or framework. /// - private static void InitializeTargetName() { - TARGET_NAME = UnityEditor.iOS.Xcode.PBXProject.GetUnityTargetName(); + /// Name of the Unity Xcode build target. + private static string InitializeTargetName() { + // NOTE: Unity-iPhone is hard coded in UnityEditor.iOS.Xcode.PBXProject and will soon longer + // be exposed via GetUnityTargetName(). It hasn't changed in many years though so we'll use + // this constant as a relatively safe default for users of the TARGET_NAME variable. + TARGET_NAME = XcodeMainTargetName; + return TARGET_NAME; } // Fix loading of the Xcode extension dll. @@ -741,6 +926,7 @@ public static void RemapXcodeExtension() { /// internal static void RestoreDefaultSettings() { settings.DeleteKeys(PREFERENCE_KEYS); + analytics.RestoreDefaultSettings(); } /// @@ -803,7 +989,27 @@ private static bool LegacyCocoapodsInstallEnabled { public static bool PodfileGenerationEnabled { get { return settings.GetBool(PREFERENCE_PODFILE_GENERATION_ENABLED, defaultValue: true); } - set { settings.SetBool(PREFERENCE_PODFILE_GENERATION_ENABLED, value); } + set { + settings.SetBool(PREFERENCE_PODFILE_GENERATION_ENABLED, value); + SetEnablePollTargetSdks(value); + } + } + + /// + /// Enable / disable polling of target iOS and tvOS SDK project setting values. + /// + private static void SetEnablePollTargetSdks(bool enable) { + if (enable && EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS) { + RunOnMainThread.OnUpdate += PollTargetIosSdk; + } else { + RunOnMainThread.OnUpdate -= PollTargetIosSdk; + } + + if (enable && EditorUserBuildSettings.activeBuildTarget == BuildTarget.tvOS) { + RunOnMainThread.OnUpdate += PollTargetTvosSdk; + } else { + RunOnMainThread.OnUpdate -= PollTargetTvosSdk; + } } /// @@ -815,6 +1021,15 @@ public static bool PodToolExecutionViaShellEnabled { set { settings.SetBool(PREFERENCE_POD_TOOL_EXECUTION_VIA_SHELL_ENABLED, value); } } + /// + /// Enable / disable setting of environment variables when executing pod tool via the shell. + /// + public static bool PodToolShellExecutionSetLang { + get { return settings.GetBool(PREFERENCE_POD_TOOL_SHELL_EXECUTION_SET_LANG, + defaultValue: true); } + set { settings.SetBool(PREFERENCE_POD_TOOL_SHELL_EXECUTION_SET_LANG, value); } + } + /// /// Enable automated pod tool installation in the editor. This is only performed when the /// editor isn't launched in batch mode. @@ -838,7 +1053,14 @@ public static bool UpgradeToWorkspaceWarningDisabled { /// public static bool VerboseLoggingEnabled { get { return settings.GetBool(PREFERENCE_VERBOSE_LOGGING_ENABLED, defaultValue: false); } - set { settings.SetBool(PREFERENCE_VERBOSE_LOGGING_ENABLED, value); } + set { + settings.SetBool(PREFERENCE_VERBOSE_LOGGING_ENABLED, value); + UpdateLoggerLevel(value); + } + } + + private static void UpdateLoggerLevel(bool verboseLoggingEnabled) { + logger.Level = verboseLoggingEnabled ? LogLevel.Verbose : LogLevel.Info; } /// @@ -851,6 +1073,92 @@ public static bool SkipPodInstallWhenUsingWorkspaceIntegration { value); } } + /// + /// Whether to add "use_frameworks!" in the Podfile. True by default. + /// If true, iOS Resolver adds the following line to Podfile. + /// + /// use_frameworks! + /// + public static bool PodfileAddUseFrameworks { + get { return settings.GetBool(PREFERENCE_PODFILE_ADD_USE_FRAMEWORKS, + defaultValue: true); } + set { + settings.SetBool(PREFERENCE_PODFILE_ADD_USE_FRAMEWORKS, value); + } + } + + /// + /// Whether to statically link framework in the Podfile. False by default. + /// If true and PodfileAddUseFrameworks is true, iOS Resolver adds the following line to Podfile. + /// + /// use_frameworks! :linkage => :static + /// + public static bool PodfileStaticLinkFrameworks { + get { return settings.GetBool(PREFERENCE_PODFILE_STATIC_LINK_FRAMEWORKS, + defaultValue: true); } + set { + settings.SetBool(PREFERENCE_PODFILE_STATIC_LINK_FRAMEWORKS, value); + } + } + + /// + /// Whether to enable Swift Framework support workaround. + /// If enabled, iOS Resolver adds a Dummy.swift to the generated Xcode project, and change build + // properties in order to properly include Swift Standard Libraries. + /// + public static bool SwiftFrameworkSupportWorkaroundEnabled { + get { return settings.GetBool(PREFERENCE_SWIFT_FRAMEWORK_SUPPORT_WORKAROUND, + defaultValue: true); } + set { + settings.SetBool(PREFERENCE_SWIFT_FRAMEWORK_SUPPORT_WORKAROUND, value); + } + } + + /// + /// The value used to set Xcode build property: Swift Language Version (SWIFT_VERSION). + /// It is default to "5" but Xcode may add or remove options over time. + /// If blank, iOS Resolver will not override the build property. + /// Please check the build property "Swift Language Version" options in Xcode project first + /// before changing this value. + /// + public static string SwiftLanguageVersion { + get { return settings.GetString(PREFERENCE_SWIFT_LANGUAGE_VERSION, + defaultValue: "5.0"); } + set { + settings.SetString(PREFERENCE_SWIFT_LANGUAGE_VERSION, value); + } + } + + /// + /// Whether to add the main target to Podfile for Unity 2019.3+. True by default. + /// If true, iOS Resolver will add the following lines to Podfile, on top of 'UnityFramework' + /// target. + /// + /// target 'Unity-iPhone' do + /// end + /// + /// This target will empty by default. + /// + public static bool PodfileAlwaysAddMainTarget { + get { return settings.GetBool(PREFERENCE_PODFILE_ALWAYS_ADD_MAIN_TARGET, + defaultValue: true); } + set { + settings.SetBool(PREFERENCE_PODFILE_ALWAYS_ADD_MAIN_TARGET, value); + } + } + + /// + /// Whether to allow the same pods to be in multiple targets, if specified in Dependecies.xml. + /// + public static bool PodfileAllowPodsInMultipleTargets { + get { return settings.GetBool(PREFERENCE_PODFILE_ALLOW_PODS_IN_MULTIPLE_TARGETS, + defaultValue: true); } + set { + settings.SetBool(PREFERENCE_PODFILE_ALLOW_PODS_IN_MULTIPLE_TARGETS, value); + } + } + + /// /// Whether to use project level settings. /// @@ -884,7 +1192,7 @@ private static bool UnityCanLoadWorkspace { } // If Unity was launched from Unity Cloud Build the build pipeline does not // open the xcworkspace so we need to force project level integration of frameworks. - if (System.Environment.CommandLine.Contains("-bvrbuildtarget")) { + if (System.Environment.CommandLine.ToLower().Contains("-bvrbuildtarget")) { return false; } return (VersionHandler.GetUnityVersionMajorMinor() >= 5.6f - epsilon); @@ -920,7 +1228,8 @@ private static bool CocoapodsProjectIntegrationEnabled { /// public static bool CocoapodsIntegrationEnabled { get { - return EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS && + return (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS || + EditorUserBuildSettings.activeBuildTarget == BuildTarget.tvOS) && CocoapodsIntegrationMethodPref != CocoapodsIntegrationMethod.None; } } @@ -930,6 +1239,18 @@ private delegate void LogMessageDelegate(string message, bool verbose = false, private static Google.Logger logger = new Google.Logger(); + // Analytics reporter. + internal static EditorMeasurement analytics = new EditorMeasurement( + settings, logger, VersionHandlerImpl.GA_TRACKING_ID, + VersionHandlerImpl.MEASUREMENT_ID, VersionHandlerImpl.PLUGIN_SUITE_NAME, "", + VersionHandlerImpl.PRIVACY_POLICY) { + BasePath = "/iosresolver/", + BaseQuery = String.Format("version={0}", IOSResolverVersionNumber.Value.ToString()), + BaseReportName = "iOS Resolver: ", + InstallSourceFilename = Assembly.GetAssembly(typeof(IOSResolver)).Location, + DataUsageUrl = VersionHandlerImpl.DATA_USAGE_URL + }; + /// /// Log a message. /// @@ -939,8 +1260,6 @@ private delegate void LogMessageDelegate(string message, bool verbose = false, /// Severity of the message. internal static void Log(string message, bool verbose = false, LogLevel level = LogLevel.Info) { - logger.Level = (VerboseLoggingEnabled || ExecutionEnvironment.InBatchMode) ? - LogLevel.Verbose : LogLevel.Info; logger.Log(message, level: verbose ? LogLevel.Verbose : level); } @@ -949,7 +1268,9 @@ internal static void Log(string message, bool verbose = false, /// internal static void LogToDialog(string message, bool verbose = false, LogLevel level = LogLevel.Info) { - if (!verbose) EditorUtility.DisplayDialog("iOS Resolver", message, "OK"); + if (!verbose) { + DialogWindow.Display("iOS Resolver", message, DialogWindow.Option.Selected0, "OK"); + } Log(message, verbose: verbose, level: level); } @@ -965,7 +1286,8 @@ public static bool PodPresent(string pod) { /// project. /// private static bool InjectDependencies() { - return EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS && + return (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS || + EditorUserBuildSettings.activeBuildTarget == BuildTarget.tvOS) && Enabled && pods.Count > 0; } @@ -1018,7 +1340,38 @@ public static void AddPod(string podName, string version = null, AddPodInternal(podName, preformattedVersion: PodVersionExpressionFromVersionDep(version), bitcodeEnabled: bitcodeEnabled, minTargetSdk: minTargetSdk, - sources: sources); + addToAllTargets: false, sources: sources); + } + + /// + /// Tells the app what pod dependencies are needed. + /// This is called from a deps file in each API to aggregate all of the + /// dependencies to automate the Podfile generation. + /// + /// pod path, for example "Google-Mobile-Ads-SDK" to + /// be included + /// Version specification. + /// See PodVersionExpressionFromVersionDep for how the version string is processed. + /// Whether the pod was compiled with bitcode + /// enabled. If this is set to false on a pod, the entire project will + /// be configured with bitcode disabled. + /// Minimum SDK revision required by this + /// pod. + /// Whether to add this pod to all targets when multiple + /// target is supported. + /// List of sources to search for all pods. + /// Each source is a URL that is injected in the source section of a Podfile + /// See https://guides.cocoapods.org/syntax/podfile.html#source for the description of + /// a source. + public static void AddPod(string podName, string version = null, + bool bitcodeEnabled = true, + string minTargetSdk = null, + bool addToAllTargets = false, + IEnumerable sources = null) { + AddPodInternal(podName, + preformattedVersion: PodVersionExpressionFromVersionDep(version), + bitcodeEnabled: bitcodeEnabled, minTargetSdk: minTargetSdk, + addToAllTargets: addToAllTargets, sources: sources); } /// @@ -1034,6 +1387,8 @@ public static void AddPod(string podName, string version = null, /// be configured with bitcode disabled. /// Minimum SDK revision required by this /// pod. + /// Whether to add this pod to all targets when multiple + /// target is supported. /// List of sources to search for all pods. /// Each source is a URL that is injected in the source section of a Podfile /// See https://guides.cocoapods.org/syntax/podfile.html#source for the description of @@ -1047,22 +1402,24 @@ private static void AddPodInternal(string podName, string preformattedVersion = null, bool bitcodeEnabled = true, string minTargetSdk = null, + bool addToAllTargets = false, IEnumerable sources = null, bool overwriteExistingPod = true, string createdBy = null, bool fromXmlFile = false, Dictionary propertiesByName = null) { var pod = new Pod(podName, preformattedVersion, bitcodeEnabled, minTargetSdk, - sources, propertiesByName); + addToAllTargets, sources, propertiesByName); pod.createdBy = createdBy ?? pod.createdBy; pod.fromXmlFile = fromXmlFile; Log(String.Format( - "AddPod - name: {0} version: {1} bitcode: {2} sdk: {3} sources: {4}, " + - "properties: {5}\n" + - "createdBy: {6}\n\n", + "AddPod - name: {0} version: {1} bitcode: {2} sdk: {3} alltarget: {4} " + + "sources: {5} properties: {6}\n" + + "createdBy: {7}\n\n", podName, preformattedVersion ?? "null", bitcodeEnabled.ToString(), minTargetSdk ?? "null", + addToAllTargets.ToString(), sources != null ? String.Join(", ", (new List(sources)).ToArray()) : "(null)", Pod.PropertyDictionaryToString(pod.propertiesByName), createdBy ?? pod.createdBy), @@ -1081,95 +1438,150 @@ private static void AddPodInternal(string podName, return; } pods[podName] = pod; + ScheduleCheckTargetIosSdkVersion(); + ScheduleCheckTargetTvosSdkVersion(); + } - UpdateTargetSdk(pod); + /// + /// Determine whether the target iOS SDK has changed. + /// + private static void PollTargetIosSdk() { + iosTargetSdkPoller.Poll(() => TargetIosSdkVersionString, + (previousValue, currentValue) => { ScheduleCheckTargetIosSdkVersion(); }); } /// - /// Update the iOS target SDK if it's lower than the minimum SDK - /// version specified by the pod. + /// Determine whether the target tvOS SDK has changed. /// - /// Pod to query for the minimum supported version. - /// - /// Whether to write to the log to notify the - /// user of a build setting change. - /// true if the SDK version was changed, false - /// otherwise. - private static bool UpdateTargetSdk(Pod pod, - bool notifyUser = true) { - int currentVersion = TargetSdkVersion; - int minVersion = pod.MinTargetSdkToVersion(); - if (currentVersion >= minVersion) { - return false; - } - if (notifyUser) { - string oldSdk = TargetSdk; - TargetSdkVersion = minVersion; - Log("iOS Target SDK changed from " + oldSdk + " to " + - TargetSdk + " required by the " + pod.name + " pod"); + private static void PollTargetTvosSdk() { + tvosTargetSdkPoller.Poll(() => TargetTvosSdkVersionString, + (previousValue, currentValue) => { ScheduleCheckTargetTvosSdkVersion(); }); + } + + // ID of the job which checks that the target iOS SDK is correct for the currently selected + // set of Cocoapods. + private static int checkIosTargetSdkVersionJobId = 0; + + /// + /// Schedule a check to ensure target iOS SDK is configured correctly given the + /// set of selected Cocoapods. + /// + private static void ScheduleCheckTargetIosSdkVersion() { + if(EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS) { + RunOnMainThread.Cancel(checkIosTargetSdkVersionJobId); + checkIosTargetSdkVersionJobId = RunOnMainThread.Schedule(() => { + UpdateTargetIosSdkVersion(false); + }, 500.0 /* delay in milliseconds before running the check */); + } + } + + // ID of the job which checks that the target tvOS SDK is correct for the + // currently selected set of Cocoapods. + private static int checkTvosTargetSdkJobId = 0; + + /// + /// Schedule a check to ensure target tvOS SDK is configured correctly given the + /// set of selected Cocoapods. + /// + private static void ScheduleCheckTargetTvosSdkVersion() { + if(EditorUserBuildSettings.activeBuildTarget == BuildTarget.tvOS) { + RunOnMainThread.Cancel(checkTvosTargetSdkJobId); + checkTvosTargetSdkJobId = RunOnMainThread.Schedule(() => { + UpdateTargetTvosSdkVersion(false); + }, 500.0 /* delay in milliseconds before running the check */); + } + } + + /// + /// Update the iOS target SDK if it's required. + /// + /// Whether the build is being processed. + public static void UpdateTargetIosSdkVersion(bool runningBuild) { + var minVersionAndPodNames = TargetSdkNeedsUpdate(TargetIosSdkVersionNum); + if (minVersionAndPodNames.Value != null) { + var minVersionString = TargetSdkVersionToString(minVersionAndPodNames.Key); + DialogWindow.Display("Unsupported Target iOS SDK", + String.Format( + TARGET_SDK_NEEDS_UPDATE_STRING, /*platformName=*/"iOS", + TargetIosSdkVersionNum, minVersionString, + String.Join(", ", minVersionAndPodNames.Value.ToArray())), + DialogWindow.Option.Selected1 /* No */, "Yes", "No", + complete: (selectedOption) => { + analytics.Report( + "UpdateTargetIosSdkVersion/" + + (selectedOption == DialogWindow.Option.Selected0 ? "apply" : "cancel"), + "Update Target iOS SDK"); + if (selectedOption == DialogWindow.Option.Selected0) { + TargetIosSdkVersionNum = minVersionAndPodNames.Key; + if (runningBuild) { + string errorString = String.Format( + UPDATED_TARGET_SDK_STRING, /*platform=*/"iOS", + TargetIosSdkVersionString, minVersionString); + DialogWindow.Display("Target iOS SDK updated.", errorString, + DialogWindow.Option.Selected0, "OK"); + } + } + }); } - return true; } /// - /// Update the target SDK if it's required. + /// Update the iOS target SDK if it's required. /// - /// true if the SDK was updated, false otherwise. - public static bool UpdateTargetSdk() { - var minVersionAndPodNames = TargetSdkNeedsUpdate(); + /// Whether the build is being processed. + public static void UpdateTargetTvosSdkVersion(bool runningBuild) { + var minVersionAndPodNames = TargetSdkNeedsUpdate(TargetTvosSdkVersionNum); if (minVersionAndPodNames.Value != null) { var minVersionString = TargetSdkVersionToString(minVersionAndPodNames.Key); - var update = EditorUtility.DisplayDialog( - "Unsupported Target SDK", - "Target SDK selected in the iOS Player Settings (" + - TargetSdk + ") is not supported by the Cocoapods " + - "included in this project. " + - "The build will very likely fail. The minimum supported " + - "version is \"" + minVersionString + "\" " + - "required by pods (" + - String.Join(", ", minVersionAndPodNames.Value.ToArray()) + - ").\n" + - "Would you like to update the target SDK version?", - "Yes", cancel: "No"); - if (update) { - TargetSdkVersion = minVersionAndPodNames.Key; - string errorString = ( - "Target SDK has been updated from " + TargetSdk + - " to " + minVersionString + ". You must restart the " + - "build for this change to take effect."); - EditorUtility.DisplayDialog( - "Target SDK updated.", errorString, "OK"); - return true; - } + DialogWindow.Display("Unsupported Target tvOS SDK", + String.Format( + TARGET_SDK_NEEDS_UPDATE_STRING, /*platformName=*/"tvOS", + TargetTvosSdkVersionNum, minVersionString, + String.Join(", ", minVersionAndPodNames.Value.ToArray())), + DialogWindow.Option.Selected1 /* No */, "Yes", "No", + complete: (selectedOption) => { + analytics.Report( + "UpdateTargetTvosSdkVersion/" + + (selectedOption == DialogWindow.Option.Selected0 ? "apply" : "cancel"), + "Update Target tvOS SDK"); + if (selectedOption == DialogWindow.Option.Selected0) { + TargetTvosSdkVersionNum = minVersionAndPodNames.Key; + if (runningBuild) { + string errorString = String.Format( + UPDATED_TARGET_SDK_STRING, /*platform=*/"tvOS", + TargetTvosSdkVersionString, minVersionString); + DialogWindow.Display("Target tvOS SDK updated.", errorString, + DialogWindow.Option.Selected0, "OK"); + } + } + }); } - return false; } /// /// Determine whether the target SDK needs to be updated based upon pod /// dependencies. /// - /// Key value pair of minimum SDK version (key) and + /// Key value pair of maximum of the minimum SDK versions (key) and /// a list of pod names that require it (value) if the currently /// selected target SDK version does not satisfy pod requirements, the list /// (value) is null otherwise. - private static KeyValuePair> TargetSdkNeedsUpdate() { - var kvpair = new KeyValuePair>(0, null); - var podListsByVersion = Pod.BucketByMinSdkVersion(pods.Values); - if (podListsByVersion.Count == 0) { - return kvpair; - } - KeyValuePair> minVersionAndPodName = kvpair; - foreach (var versionAndPodList in podListsByVersion) { - minVersionAndPodName = versionAndPodList; - break; - } - int currentVersion = TargetSdkVersion; - if (currentVersion >= minVersionAndPodName.Key) { - return kvpair; + private static KeyValuePair> + TargetSdkNeedsUpdate(int targetSdkVersionNum) { + var emptyVersionAndPodNames = new KeyValuePair>(0, null); + var minVersionAndPodNames = emptyVersionAndPodNames; + int maxOfMinRequiredVersions = 0; + foreach (var versionAndPodList in Pod.BucketByMinSdkVersion(pods.Values)) { + if (versionAndPodList.Key > maxOfMinRequiredVersions) { + maxOfMinRequiredVersions = versionAndPodList.Key; + minVersionAndPodNames = versionAndPodList; + } } - return minVersionAndPodName; + // If the target SDK version exceeds the minimum required version return an empty tuple + // otherwise return the minimum required SDK version and the set of pods that need it. + return targetSdkVersionNum >= maxOfMinRequiredVersions ? emptyVersionAndPodNames : + minVersionAndPodNames; } // Get the path of an xcode project relative to the specified directory. @@ -1191,42 +1603,19 @@ public static string GetProjectPath(string relativeTo) { /// /// Get or set the Unity iOS target SDK version string (e.g "7.1") - /// build setting. + /// build setting dependeding on the current active build target. /// - static string TargetSdk { + static string TargetIosSdkVersionString { get { string name = null; var iosSettingsType = typeof(UnityEditor.PlayerSettings.iOS); - // Read the version (Unity 5.5 and above). - var osVersionProperty = iosSettingsType.GetProperty( - "targetOSVersionString"); + var osVersionProperty = + iosSettingsType.GetProperty("targetOSVersionString"); if (osVersionProperty != null) { name = (string)osVersionProperty.GetValue(null, null); } - if (name == null) { - // Read the version (deprecated in Unity 5.5). - osVersionProperty = iosSettingsType.GetProperty( - "targetOSVersion"); - if (osVersionProperty != null) { - var osVersionValue = - osVersionProperty.GetValue(null, null); - if (osVersionValue != null) { - name = Enum.GetName(osVersionValue.GetType(), - osVersionValue); - } - } - } if (String.IsNullOrEmpty(name)) { - // Versions 8.2 and above do not have enum symbols - // The values in Unity 5.4.1f1: - // 8.2 == 32 - // 8.3 == 34 - // 8.4 == 36 - // 9.0 == 38 - // 9.1 == 40 - // Since these are undocumented just report - // 8.2 as selected for the moment. - return TargetSdkVersionToString(DEFAULT_TARGET_SDK); + return TargetSdkVersionToString(DEFAULT_IOS_TARGET_SDK); } return name.Trim().Replace("iOS_", "").Replace("_", "."); } @@ -1244,24 +1633,60 @@ static string TargetSdk { osVersionProperty.SetValue( null, Enum.Parse(osVersionProperty.PropertyType, - "iOS_" + value.Replace(".", "_")), + "iOS_" + value.Replace(".", "_")), null); } } } + /// + /// Get or set the Unity tvOS target SDK version string (e.g "7.1") + /// build setting dependeding on the current active build target. + /// + static string TargetTvosSdkVersionString { + get { + string name = null; + var tvosSettingsType = typeof(UnityEditor.PlayerSettings.tvOS); + var osVersionProperty = + tvosSettingsType.GetProperty("targetOSVersionString"); + if (osVersionProperty != null) { + name = (string)osVersionProperty.GetValue(null, null); + } + if (String.IsNullOrEmpty(name)) { + return DEFAULT_TVOS_TARGET_SDK; + } + return name.Trim().Replace("tvOS_", "").Replace("_", "."); + } + + set { + var tvosSettingsType = typeof(UnityEditor.PlayerSettings.tvOS); + var osVersionProperty = + tvosSettingsType.GetProperty("targetOSVersionString"); + osVersionProperty.SetValue(null, value, null); + } + } + /// /// Get or set the Unity iOS target SDK using a version number (e.g 71 /// is equivalent to "7.1"). /// - static int TargetSdkVersion { - get { return TargetSdkStringToVersion(TargetSdk); } - set { TargetSdk = TargetSdkVersionToString(value); } + static int TargetIosSdkVersionNum { + get { return TargetSdkStringToVersion(TargetIosSdkVersionString); } + set { TargetIosSdkVersionString = TargetSdkVersionToString(value); } + } + + /// + /// Get or set the Unity iOS target SDK using a version number (e.g 71 + /// is equivalent to "7.1"). + /// + static int TargetTvosSdkVersionNum { + get { return TargetSdkStringToVersion(TargetTvosSdkVersionString); } + set { TargetTvosSdkVersionString = TargetSdkVersionToString(value); } } /// /// Convert a target SDK string into a value of the form - // (major * 10) + minor. + /// (major * 10) + minor. /// /// Integer representation of the SDK. internal static int TargetSdkStringToVersion(string targetSdk) { @@ -1278,9 +1703,9 @@ internal static int TargetSdkStringToVersion(string targetSdk) { "Please change this to a valid SDK version (e.g {1}) in:\n" + " Player Settings -> Other Settings --> " + "Target Minimum iOS Version\n", - targetSdk, TargetSdkVersionToString(DEFAULT_TARGET_SDK)), + targetSdk, TargetSdkVersionToString(DEFAULT_IOS_TARGET_SDK)), level: LogLevel.Warning); - return DEFAULT_TARGET_SDK; + return DEFAULT_IOS_TARGET_SDK; } @@ -1297,7 +1722,7 @@ internal static string TargetSdkVersionToString(int version) { /// /// Determine whether any pods need bitcode disabled. /// - /// List of pod names with bitcode disabled. + /// List of pod names with bitcode disabled. private static List FindPodsWithBitcodeDisabled() { var disabled = new List(); foreach (var pod in pods.Values) { @@ -1311,7 +1736,7 @@ private static List FindPodsWithBitcodeDisabled() { /// /// Menu item that installs CocoaPods if it's not already installed. /// - [MenuItem("Assets/Play Services Resolver/iOS Resolver/Install Cocoapods")] + [MenuItem("Assets/External Dependency Manager/iOS Resolver/Install Cocoapods")] public static void InstallCocoapodsMenu() { InstallCocoapodsInteractive(); } @@ -1403,7 +1828,9 @@ public static void InstallCocoapods(bool interactive, string workingDirectory, var podToolPath = FindPodTool(); if (!String.IsNullOrEmpty(podToolPath)) { var installationFoundMessage = "CocoaPods installation detected " + podToolPath; - if (displayAlreadyInstalled) logMessage(installationFoundMessage); + if (displayAlreadyInstalled) { + logMessage(installationFoundMessage, level: LogLevel.Verbose); + } cocoapodsToolsInstallPresent = true; return; } @@ -1416,7 +1843,9 @@ public static void InstallCocoapods(bool interactive, string workingDirectory, "For more information see:\n" + " https://guides.cocoapods.org/using/getting-started.html\n\n"; - // Log the set of install pods. + analytics.Report("installpodtool/querygems", "Install Pod Tool Query Ruby Gems"); + + // Log the set of install gems. RunCommand(GEM_EXECUTABLE, "list"); // Gem is being executed in an RVM directory it's already configured to perform a @@ -1440,6 +1869,7 @@ public static void InstallCocoapods(bool interactive, string workingDirectory, installArgs += " --verbose"; } + analytics.Report("installpodtool/installgems", "Install Pod Tool Ruby Gem"); var commandList = new List(); if (!QueryGemInstalled("activesupport", logMessage: logMessage)) { // Workaround activesupport (dependency of the CocoaPods gem) requiring @@ -1464,6 +1894,9 @@ public static void InstallCocoapods(bool interactive, string workingDirectory, var lastCommand = commands[commandIndex]; commandIndex += 1; if (result.exitCode != 0) { + analytics.Report("installpodtool/failed", + String.Format("Install Pod Tool Ruby Gem Failed {0}", + result.exitCode)); logMessage(String.Format( "Failed to install CocoaPods for the current user.\n\n" + "{0}\n" + @@ -1484,15 +1917,20 @@ public static void InstallCocoapods(bool interactive, string workingDirectory, "'{0} {1}' succeeded but the {2} tool cannot be found.\n\n" + "{3}\n", lastCommand.Command, lastCommand.Arguments, POD_EXECUTABLE, commonInstallErrorMessage), level: LogLevel.Error); + analytics.Report("installpodtool/failedmissing", + "Install Pod Tool Ruby Gem Succeeded, Missing Tool"); complete.Set(); return -1; } if (dialog != null) { + analytics.Report("installpodtool/downloadrepo", + "Install Pod Tool Download Cocoapods Repo"); dialog.bodyText += ("\n\nDownloading CocoaPods Master Repository\n" + "(this can take a while)\n"); } commands[commandIndex].Command = podToolPath; } else if (commandIndex == commands.Length) { + analytics.Report("installpodtool/success", "Install Pod Tool Succeeded"); complete.Set(); logMessage("CocoaPods tools successfully installed."); cocoapodsToolsInstallPresent = true; @@ -1504,6 +1942,29 @@ public static void InstallCocoapods(bool interactive, string workingDirectory, if (!interactive) complete.WaitOne(); } + /// + /// Called by Unity when all assets have been updated. This refreshes the Pods loaded from + /// XML files. + /// + /// Imported assets. (unused) + /// Deleted assets. (unused) + /// Moved assets. (unused) + /// Moved from asset paths. (unused) + private static void OnPostprocessAllAssets(string[] importedAssets, + string[] deletedAssets, + string[] movedAssets, + string[] movedFromAssetPaths) { + if (!CocoapodsIntegrationEnabled) return; + bool dependencyFileChanged = false; + var changedAssets = new List(importedAssets); + changedAssets.AddRange(deletedAssets); + foreach (var asset in changedAssets) { + dependencyFileChanged = xmlDependencies.IsDependenciesFile(asset); + if (dependencyFileChanged) break; + } + if (dependencyFileChanged) RefreshXmlDependencies(); + } + /// /// Refresh XML dependencies if the plugin is enabled. /// @@ -1540,6 +2001,89 @@ public static void OnPostProcessPatchProject(BuildTarget buildTarget, PatchProject(buildTarget, pathToBuiltProject); } + /// + /// Post-processing build step to add dummy swift file + /// + [PostProcessBuildAttribute(BUILD_ORDER_PATCH_PROJECT)] + public static void OnPostProcessAddDummySwiftFile(BuildTarget buildTarget, + string pathToBuiltProject) { + if (!InjectDependencies() || + !PodfileGenerationEnabled || + !PodfileAddUseFrameworks || + !SwiftFrameworkSupportWorkaroundEnabled) { + return; + } + AddDummySwiftFile(buildTarget, pathToBuiltProject); + } + + /// + /// Get Xcode target names using a method that works across all Unity versions. + /// + /// List of Xcode target names for build targets that could depend upon + /// Cocoapods. + public static IEnumerable XcodeTargetNames { + get { + // Return hard coded names in the UnityEditor.iOS.Xcode.PBXProject DLL. + return MultipleXcodeTargetsSupported ? + new List() { XcodeUnityFrameworkTargetName } : + new List() { InitializeTargetName() }; + } + } + /// + /// Get Xcode target GUIDs using a method that works across all Unity versions. + /// + /// UnityEditor.iOS.Xcode.PBXProject project to query. + /// List of target GUIDs. + public static IEnumerable GetXcodeTargetGuids(object xcodeProject) { + return GetXcodeTargetGuids(xcodeProject, includeAllTargets: false); + } + + /// + /// Get Xcode target GUIDs using a method that works across all Unity versions. + /// + /// UnityEditor.iOS.Xcode.PBXProject project to query. + /// If true, if multiple xcode project targets is supported, ex. + /// Unity 2019.3+, returns both guids of 'UnityFramework' and the main target 'Unity-iPhone`. + /// Otherwise, only return the guid of the target which contains Unity libraries. For Unity + /// 2019.2 or below, it is the guid of `Unity-iPhone`; for Unity 2019.3+, it is the guid of + /// `UnityFramework`. + /// + /// List of target GUIDs. + public static IEnumerable GetXcodeTargetGuids(object xcodeProject, + bool includeAllTargets) { + var project = (UnityEditor.iOS.Xcode.PBXProject)xcodeProject; + var targets = new List(); + if (MultipleXcodeTargetsSupported) { + // In Unity 2019.3+ TargetGuidByName will throw an exception if the Unity-iPhone target + // is requested so we need to use instance methods to fetch the GUIDs of each target. + // NOTE: The test target is not exposed. + try { + var guidMethods = new List() {"GetUnityFrameworkTargetGuid"}; + if (includeAllTargets) { + guidMethods.Add("GetUnityMainTargetGuid"); + } + foreach (var guidMethod in guidMethods) { + targets.Add((string)VersionHandler.InvokeInstanceMethod(project, guidMethod, + null)); + } + } catch (Exception exception) { + Log(String.Format( + "Failed to get the Xcode target GUIDs, it's possible Unity has broken " + + "the Xcode API {0}. Please report a bug to " + + "https://github.com/googlesamples/unity-jar-resolver/issues", + exception), level: LogLevel.Error); + throw exception; + } + } else { + // TargetGuidByName in Unity < 2019.3 can be used to lookup any target GUID. + foreach (var targetName in XcodeTargetNames) { + targets.Add((string)VersionHandler.InvokeInstanceMethod( + project, "TargetGuidByName", new [] { (object)targetName })); + } + } + return targets; + } + // Implementation of OnPostProcessPatchProject(). // NOTE: This is separate from the post-processing method to prevent the // Mono runtime from loading the Xcode API before calling the post @@ -1557,17 +2101,53 @@ internal static void PatchProject( string pbxprojPath = GetProjectPath(pathToBuiltProject); var project = new UnityEditor.iOS.Xcode.PBXProject(); project.ReadFromString(File.ReadAllText(pbxprojPath)); - string target = project.TargetGuidByName(TARGET_NAME); - project.SetBuildProperty(target, "CLANG_ENABLE_MODULES", "YES"); - project.AddBuildProperty(target, "OTHER_LDFLAGS", "-ObjC"); - // GTMSessionFetcher requires Obj-C exceptions. - project.SetBuildProperty(target, "GCC_ENABLE_OBJC_EXCEPTIONS", "YES"); - if (bitcodeDisabled) { - project.AddBuildProperty(target, "ENABLE_BITCODE", "NO"); + foreach (var target in GetXcodeTargetGuids(project)) { + project.SetBuildProperty(target, "CLANG_ENABLE_MODULES", "YES"); + project.AddBuildProperty(target, "OTHER_LDFLAGS", "-ObjC"); + // GTMSessionFetcher requires Obj-C exceptions. + project.SetBuildProperty(target, "GCC_ENABLE_OBJC_EXCEPTIONS", "YES"); + if (bitcodeDisabled) { + project.AddBuildProperty(target, "ENABLE_BITCODE", "NO"); + } } File.WriteAllText(pbxprojPath, project.WriteToString()); } + internal static void AddDummySwiftFile( + BuildTarget buildTarget, string pathToBuiltProject) { + string pbxprojPath = GetProjectPath(pathToBuiltProject); + var project = new UnityEditor.iOS.Xcode.PBXProject(); + project.ReadFromString(File.ReadAllText(pbxprojPath)); + + string DUMMY_SWIFT_FILE_NAME = "Dummy.swift"; + string DUMMY_SWIFT_FILE_CONTENT = + "// Generated by External Dependency Manager for Unity\n" + + "import Foundation"; + string dummySwiftPath = Path.Combine(pathToBuiltProject, DUMMY_SWIFT_FILE_NAME); + if (!File.Exists(dummySwiftPath)) { + File.WriteAllText(dummySwiftPath, DUMMY_SWIFT_FILE_CONTENT); + } + + foreach (var target in GetXcodeTargetGuids(project, includeAllTargets: false)) { + project.AddFileToBuild( + target, + project.AddFile(DUMMY_SWIFT_FILE_NAME, + DUMMY_SWIFT_FILE_NAME, + UnityEditor.iOS.Xcode.PBXSourceTree.Source)); + if(!string.IsNullOrEmpty(SwiftLanguageVersion)) { + project.SetBuildProperty(target, "SWIFT_VERSION", SwiftLanguageVersion); + } + + // These build properties are only required for multi-target Xcode project, which is + // generated from 2019.3+. + if(MultipleXcodeTargetsSupported) { + project.SetBuildProperty(target, "CLANG_ENABLE_MODULES", "YES"); + } + } + + File.WriteAllText(pbxprojPath, project.WriteToString()); + } + /// /// Post-processing build step to generate the podfile for ios. /// @@ -1623,12 +2203,17 @@ private static void ParseUnityDeps(string unityPodfilePath) { var sources = new List(); while ((line = unityPodfile.ReadLine()) != null) { line = line.Trim(); + if (PODFILE_COMMENT_REGEX.IsMatch(line)) { + continue; + } var sourceLineMatch = PODFILE_SOURCE_REGEX.Match(line); if (sourceLineMatch.Groups.Count > 1) { sources.Add(sourceLineMatch.Groups[1].Value); continue; } - if (line.StartsWith("target 'Unity-iPhone' do")) { + // TODO: Properly support multiple targets. + if (line.StartsWith(String.Format("target '{0}' do", + XcodeTargetWithUnityLibraries))) { capturingPodsDepth++; continue; } @@ -1684,7 +2269,7 @@ private static void ParseUnityDeps(string unityPodfilePath) { /// this returns the string... /// /// source 'http://myrepo.com/Specs.git' - /// source 'http://anotherrepo.com/Specs.git' + /// source 'http://anotherrepo.com/Specs.git' private static string GeneratePodfileSourcesSection() { var interleavedSourcesLines = new List(); var processedSources = new HashSet(); @@ -1715,6 +2300,7 @@ private static string GeneratePodfileSourcesSection() { // processing step. public static void GenPodfile(BuildTarget buildTarget, string pathToBuiltProject) { + analytics.Report("generatepodfile", "Generate Podfile"); string podfilePath = GetPodfilePath(pathToBuiltProject); string unityPodfile = FindExistingUnityPodfile(podfilePath); @@ -1734,15 +2320,88 @@ public static void GenPodfile(BuildTarget buildTarget, (CocoapodsWorkspaceIntegrationEnabled ? "Xcode workspace" : (CocoapodsProjectIntegrationEnabled ? "Xcode project" : "no target"))), verbose: true); + using (StreamWriter file = new StreamWriter(podfilePath)) { - file.Write(GeneratePodfileSourcesSection() + - String.Format("platform :ios, '{0}'\n\n", TargetSdk) + - "target '" + TARGET_NAME + "' do\n"); - foreach(var pod in pods.Values) { - file.WriteLine(pod.PodFilePodLine); + file.WriteLine(GeneratePodfileSourcesSection()); + switch (EditorUserBuildSettings.activeBuildTarget) { + case BuildTarget.iOS: + file.WriteLine(String.Format("platform :ios, '{0}'\n", + TargetIosSdkVersionString)); + break; + case BuildTarget.tvOS: + file.WriteLine(String.Format("platform :tvos, '{0}'\n", + TargetTvosSdkVersionString)); + break; + default: + throw new Exception("IOSResolver.GenPodfile() invoked for a " + + "build target other than iOS or tvOS."); + break; + } + + foreach (var target in XcodeTargetNames) { + file.WriteLine(String.Format("target '{0}' do", target)); + foreach(var pod in pods.Values) { + file.WriteLine(String.Format(" {0}", pod.PodFilePodLine)); + } + file.WriteLine("end"); + } + + if (MultipleXcodeTargetsSupported && PodfileAlwaysAddMainTarget) { + file.WriteLine(String.Format("target '{0}' do", XcodeMainTargetName)); + bool allowPodsInMultipleTargets = PodfileAllowPodsInMultipleTargets; + int podAdded = 0; + foreach(var pod in pods.Values) { + if (pod.addToAllTargets) { + file.WriteLine(String.Format(" {0}{1}", + allowPodsInMultipleTargets ? "" : "# ", + pod.PodFilePodLine)); + podAdded++; + } + } + if (!allowPodsInMultipleTargets && podAdded > 0) { + file.WriteLine(String.Format( + " # Commented due to iOS Resolver settings.")); + } + file.WriteLine("end"); + } + if (PodfileAddUseFrameworks) { + file.WriteLine( PodfileStaticLinkFrameworks ? + "use_frameworks! :linkage => :static" : + "use_frameworks!"); } - file.WriteLine("end"); } + + int versionCount = 0; + int localPathCount = 0; + int targetSdkCount = 0; + int maxProperties = 0; + int maxSources = Pod.Sources.Count; + int fromXmlFileCount = 0; + foreach (var pod in pods.Values) { + maxProperties = Math.Max(maxProperties, pod.propertiesByName.Count); + maxSources = Math.Max(maxSources, pod.sources.Count); + if (!String.IsNullOrEmpty(pod.version)) versionCount++; + if (!String.IsNullOrEmpty(pod.minTargetSdk)) targetSdkCount++; + if (!String.IsNullOrEmpty(pod.LocalPath)) localPathCount++; + if (pod.fromXmlFile) fromXmlFileCount++; + } + analytics.Report("generatepodfile/podinfo", + new KeyValuePair[] { + new KeyValuePair("numPods", pods.Count.ToString()), + new KeyValuePair("numPodsWithVersions", + versionCount.ToString()), + new KeyValuePair("numLocalPods", + localPathCount.ToString()), + new KeyValuePair("numMinTargetSdk", + targetSdkCount.ToString()), + new KeyValuePair("maxNumProperties", + maxProperties.ToString()), + new KeyValuePair("maxNumSources", + maxSources.ToString()), + new KeyValuePair("numFromXmlFiles", + fromXmlFileCount.ToString()) + }, + "Generate Podfile Pods Section"); } /// @@ -1873,7 +2532,7 @@ private class DelegateContainer { /// Delegate method associated with the container. This enables the /// following pattern: /// - /// var container = new DelegateContainer(); + /// var container = new DelegateContainer<CommandLine.CompletionHandler>(); /// container.Handler = (CommandLine.Result result) => { RunNext(container.Handler); }; /// public T Handler { get; set; } @@ -1963,7 +2622,8 @@ private static void RunCommandsAsync(CommandItem[] commands, Log(command.ToString(), verbose: true); var result = CommandLine.RunViaShell( command.Command, command.Arguments, workingDirectory: command.WorkingDirectory, - envVars: envVars, useShellExecution: PodToolExecutionViaShellEnabled); + envVars: envVars, useShellExecution: PodToolExecutionViaShellEnabled, + setLangInShellMode: PodToolShellExecutionSetLang); LogCommandLineResult(command.ToString(), result); index = completionDelegate(index, commands, result, null); } @@ -2075,7 +2735,15 @@ private static CommandLine.Result RunPodCommand( public static void OnPostProcessInstallPods(BuildTarget buildTarget, string pathToBuiltProject) { if (!InjectDependencies() || !PodfileGenerationEnabled) return; - if (UpdateTargetSdk()) return; + + if(EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS) { + UpdateTargetIosSdkVersion(true); + } + + if(EditorUserBuildSettings.activeBuildTarget == BuildTarget.tvOS) { + UpdateTargetTvosSdkVersion(true); + } + if (!CocoapodsIntegrationEnabled || !cocoapodsToolsInstallPresent) { Log(String.Format( "Cocoapod installation is disabled.\n" + @@ -2093,6 +2761,7 @@ public static void OnPostProcessInstallPods(BuildTarget buildTarget, if (UnityCanLoadWorkspace && CocoapodsIntegrationMethodPref == CocoapodsIntegrationMethod.Workspace && SkipPodInstallWhenUsingWorkspaceIntegration) { + analytics.Report("installpods/disabled", "Pod Install Disabled"); Log("Skipping pod install.", level: LogLevel.Warning); return; } @@ -2101,9 +2770,14 @@ public static void OnPostProcessInstallPods(BuildTarget buildTarget, CommandLine.Result result; result = RunPodCommand("--version", pathToBuiltProject); if (result.exitCode == 0) podsVersion = result.stdout.Trim(); + var cocoapodsVersionParameters = new KeyValuePair[] { + new KeyValuePair("cocoapodsVersion", podsVersion) + }; if (result.exitCode != 0 || (!String.IsNullOrEmpty(podsVersion) && podsVersion[0] == '0')) { + analytics.Report("installpods/outofdate", cocoapodsVersionParameters, + "Pod Install, Tool Out Of Date"); Log("Error running CocoaPods. Please ensure you have at least " + "version 1.0.0. " + COCOAPOD_INSTALL_INSTRUCTIONS + "\n\n" + "'" + POD_EXECUTABLE + " --version' returned status: " + @@ -2113,16 +2787,21 @@ public static void OnPostProcessInstallPods(BuildTarget buildTarget, return; } + analytics.Report("installpods/install", cocoapodsVersionParameters, "Pod Install"); result = RunPodCommand("install", pathToBuiltProject); // If pod installation failed it may be due to an out of date pod repo. // We'll attempt to resolve the error by updating the pod repo - // which is a slow operation - and retrying pod installation. if (result.exitCode != 0) { + analytics.Report("installpods/repoupdate", cocoapodsVersionParameters, + "Pod Install Repo Update"); CommandLine.Result repoUpdateResult = RunPodCommand("repo update", pathToBuiltProject); bool repoUpdateSucceeded = repoUpdateResult.exitCode == 0; + analytics.Report("installpods/install2", cocoapodsVersionParameters, + "Pod Install Attempt 2"); // Second attempt result. // This is isolated in case it fails, so we can just report the // original failure. @@ -2131,6 +2810,8 @@ public static void OnPostProcessInstallPods(BuildTarget buildTarget, // If the repo update still didn't fix the problem... if (result2.exitCode != 0) { + analytics.Report("installpods/failed", cocoapodsVersionParameters, + "Pod Install Failed"); Log("iOS framework addition failed due to a " + "CocoaPods installation failure. This will will likely " + "result in an non-functional Xcode project.\n\n" + @@ -2179,6 +2860,7 @@ public static void UpdateProjectDeps( // failed. var podsDir = Path.Combine(pathToBuiltProject, PODS_DIR); if (!Directory.Exists(podsDir)) return; + analytics.Report("injectpodsintoxcproj", "Inject Pods Into xcproj"); // If Unity can load workspaces, and one has been generated, yet we're still // trying to patch the project file, then we have to actually get rid of the workspace @@ -2193,7 +2875,7 @@ public static void UpdateProjectDeps( "from CocoaPods integration, however the IOSResolver Settings are configured " + "to use project level integration. It's recommended that you use workspace " + "integration instead.\n" + - "You can manage this setting from: Assets > Play Services Resolver > " + + "You can manage this setting from: Assets > External Dependency Manager > " + "iOS Resolver > Settings, using the CocoaPods Integration drop down menu.", level: LogLevel.Warning); Directory.Delete(workspacePath, true); @@ -2202,12 +2884,13 @@ public static void UpdateProjectDeps( var pbxprojPath = GetProjectPath(pathToBuiltProject); var project = new UnityEditor.iOS.Xcode.PBXProject(); project.ReadFromString(File.ReadAllText(pbxprojPath)); - var target = project.TargetGuidByName(TARGET_NAME); - var guid = project.AddFile( - Path.Combine(podsDir, PODS_PROJECT_NAME + ".xcodeproj"), - "Pods.xcodeproj", - UnityEditor.iOS.Xcode.PBXSourceTree.Source); - project.AddFileToBuild(target, guid); + foreach (var target in GetXcodeTargetGuids(project)) { + var guid = project.AddFile( + Path.Combine(podsDir, PODS_PROJECT_NAME + ".xcodeproj"), + "Pods.xcodeproj", + UnityEditor.iOS.Xcode.PBXSourceTree.Source); + project.AddFileToBuild(target, guid); + } project.WriteToFile(pbxprojPath); } diff --git a/source/IOSResolver/src/IOSResolverSettingsDialog.cs b/source/IOSResolver/src/IOSResolverSettingsDialog.cs index 96410460..e8f7001d 100644 --- a/source/IOSResolver/src/IOSResolverSettingsDialog.cs +++ b/source/IOSResolver/src/IOSResolverSettingsDialog.cs @@ -17,6 +17,7 @@ namespace Google { using System; +using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; @@ -30,26 +31,41 @@ public class IOSResolverSettingsDialog : EditorWindow /// Loads / saves settings for this dialog. /// private class Settings { - internal bool cocoapodsInstallEnabled; internal bool podfileGenerationEnabled; internal bool podToolExecutionViaShellEnabled; + internal bool podToolShellExecutionSetLang; internal bool autoPodToolInstallInEditorEnabled; internal bool verboseLoggingEnabled; internal int cocoapodsIntegrationMenuIndex; + internal bool podfileAddUseFrameworks; + internal bool podfileStaticLinkFrameworks; + internal bool swiftFrameworkSupportWorkaroundEnabled; + internal string swiftLanguageVersion; + internal bool podfileAlwaysAddMainTarget; + internal bool podfileAllowPodsInMultipleTargets; internal bool useProjectSettings; + internal EditorMeasurement.Settings analyticsSettings; /// /// Load settings into the dialog. /// internal Settings() { - cocoapodsInstallEnabled = IOSResolver.CocoapodsInstallEnabled; podfileGenerationEnabled = IOSResolver.PodfileGenerationEnabled; podToolExecutionViaShellEnabled = IOSResolver.PodToolExecutionViaShellEnabled; + podToolShellExecutionSetLang = IOSResolver.PodToolShellExecutionSetLang; autoPodToolInstallInEditorEnabled = IOSResolver.AutoPodToolInstallInEditorEnabled; verboseLoggingEnabled = IOSResolver.VerboseLoggingEnabled; cocoapodsIntegrationMenuIndex = FindIndexFromCocoapodsIntegrationMethod( IOSResolver.CocoapodsIntegrationMethodPref); + podfileAddUseFrameworks = IOSResolver.PodfileAddUseFrameworks; + podfileStaticLinkFrameworks = IOSResolver.PodfileStaticLinkFrameworks; + swiftFrameworkSupportWorkaroundEnabled = + IOSResolver.SwiftFrameworkSupportWorkaroundEnabled; + swiftLanguageVersion = IOSResolver.SwiftLanguageVersion; + podfileAlwaysAddMainTarget = IOSResolver.PodfileAlwaysAddMainTarget; + podfileAllowPodsInMultipleTargets = IOSResolver.PodfileAllowPodsInMultipleTargets; useProjectSettings = IOSResolver.UseProjectSettings; + analyticsSettings = new EditorMeasurement.Settings(IOSResolver.analytics); } /// @@ -57,13 +73,21 @@ internal Settings() { /// internal void Save() { IOSResolver.PodfileGenerationEnabled = podfileGenerationEnabled; - IOSResolver.CocoapodsInstallEnabled = cocoapodsInstallEnabled; IOSResolver.PodToolExecutionViaShellEnabled = podToolExecutionViaShellEnabled; + IOSResolver.PodToolShellExecutionSetLang = podToolShellExecutionSetLang; IOSResolver.AutoPodToolInstallInEditorEnabled = autoPodToolInstallInEditorEnabled; IOSResolver.VerboseLoggingEnabled = verboseLoggingEnabled; IOSResolver.CocoapodsIntegrationMethodPref = integrationMapping[cocoapodsIntegrationMenuIndex]; + IOSResolver.PodfileAddUseFrameworks = podfileAddUseFrameworks; + IOSResolver.PodfileStaticLinkFrameworks = podfileStaticLinkFrameworks; + IOSResolver.SwiftFrameworkSupportWorkaroundEnabled = + swiftFrameworkSupportWorkaroundEnabled; + IOSResolver.SwiftLanguageVersion = swiftLanguageVersion; + IOSResolver.PodfileAlwaysAddMainTarget = podfileAlwaysAddMainTarget; + IOSResolver.PodfileAllowPodsInMultipleTargets = podfileAllowPodsInMultipleTargets; IOSResolver.UseProjectSettings = useProjectSettings; + analyticsSettings.Save(); } } @@ -83,6 +107,8 @@ internal void Save() { IOSResolver.CocoapodsIntegrationMethod.None, }; + private Vector2 scrollPosition = new Vector2(0, 0); + // enum to index (linear search because there's no point in creating a reverse mapping // with such a small list). private static int FindIndexFromCocoapodsIntegrationMethod( @@ -94,7 +120,7 @@ private static int FindIndexFromCocoapodsIntegrationMethod( } public void Initialize() { - minSize = new Vector2(400, 360); + minSize = new Vector2(400, 715); position = new Rect(UnityEngine.Screen.width / 3, UnityEngine.Screen.height / 3, minSize.x, minSize.y); } @@ -121,6 +147,8 @@ public void OnGUI() { IOSResolverVersionNumber.Value.Minor, IOSResolverVersionNumber.Value.Build)); + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + GUILayout.BeginHorizontal(); GUILayout.Label("Podfile Generation", EditorStyles.boldLabel); settings.podfileGenerationEnabled = @@ -155,9 +183,16 @@ public void OnGUI() { settings.podToolExecutionViaShellEnabled = EditorGUILayout.Toggle(settings.podToolExecutionViaShellEnabled); GUILayout.EndHorizontal(); + GUILayout.Label("Shell execution is useful when configuration in the shell " + + "environment (e.g ~/.profile) is required to execute Cocoapods tools."); + if (settings.podToolExecutionViaShellEnabled) { - GUILayout.Label("Shell execution is useful when configuration in the shell " + - "environment (e.g ~/.profile) is required to execute Cocoapods tools."); + GUILayout.BeginHorizontal(); + GUILayout.Label("Set LANG When Using Shell to Execute Cocoapod Tool", EditorStyles.boldLabel); + settings.podToolShellExecutionSetLang = + EditorGUILayout.Toggle(settings.podToolShellExecutionSetLang); + GUILayout.EndHorizontal(); + GUILayout.Label("Useful for versions of cocoapods that depend on the value of LANG."); } GUILayout.BeginHorizontal(); @@ -169,10 +204,95 @@ public void OnGUI() { GUILayout.Label("Automatically installs the Cocoapod tool if the editor isn't " + "running in batch mode"); } else { - GUILayout.Label("Cocoapod tool installation can be performed via the menu option: " + - "Assets > Play Services Resolver > iOS Resolver > Install Cocoapods"); + GUILayout.Label( + "Cocoapod tool installation can be performed via the menu option: " + + "Assets > External Dependency Manager > iOS Resolver > Install Cocoapods"); } + if (settings.podfileGenerationEnabled) { + GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(1)); + GUILayout.Label("Podfile Configurations", EditorStyles.largeLabel); + EditorGUILayout.Separator(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Add use_frameworks! to Podfile", EditorStyles.boldLabel); + settings.podfileAddUseFrameworks = + EditorGUILayout.Toggle(settings.podfileAddUseFrameworks); + GUILayout.EndHorizontal(); + + GUILayout.Label("Add the following line to Podfile. Required if any third-party " + + "Unity packages depends on Swift frameworks."); + if (settings.podfileStaticLinkFrameworks) { + GUILayout.Label(" use_frameworks! :linkage => :static"); + } else { + GUILayout.Label(" use_frameworks!"); + } + + if (settings.podfileAddUseFrameworks) { + GUILayout.BeginHorizontal(); + GUILayout.Label("Link frameworks statically", EditorStyles.boldLabel); + settings.podfileStaticLinkFrameworks = + EditorGUILayout.Toggle(settings.podfileStaticLinkFrameworks); + GUILayout.EndHorizontal(); + GUILayout.Label("Link frameworks statically is recommended just in case any pod " + + "framework includes static libraries."); + } + + if (IOSResolver.MultipleXcodeTargetsSupported) { + GUILayout.BeginHorizontal(); + GUILayout.Label("Always add the main target to Podfile", EditorStyles.boldLabel); + settings.podfileAlwaysAddMainTarget = + EditorGUILayout.Toggle(settings.podfileAlwaysAddMainTarget); + GUILayout.EndHorizontal(); + + GUILayout.Label("Add the following lines to Podfile."); + GUILayout.Label(String.Format(" target '{0}' do\n" + + " end", IOSResolver.XcodeMainTargetName)); + + if (settings.podfileAlwaysAddMainTarget) { + GUILayout.BeginHorizontal(); + GUILayout.Label("Allow the same pod to be in multiple targets", + EditorStyles.boldLabel); + settings.podfileAllowPodsInMultipleTargets = + EditorGUILayout.Toggle(settings.podfileAllowPodsInMultipleTargets); + GUILayout.EndHorizontal(); + + GUILayout.Label("Allow to add the same pod to multiple targets, if specified in " + + "Dependencies.xml with 'addToAllTargets' attribute."); + } + } + + if (settings.podfileAddUseFrameworks) { + GUILayout.BeginHorizontal(); + GUILayout.Label("(Recommended) Enable Swift Framework Support Workaround", + EditorStyles.boldLabel); + settings.swiftFrameworkSupportWorkaroundEnabled = + EditorGUILayout.Toggle(settings.swiftFrameworkSupportWorkaroundEnabled); + GUILayout.EndHorizontal(); + GUILayout.Label("This workround patches the Xcode project to properly link Swift " + + "Standard Library when some plugins depend on Swift Framework " + + "pods by:"); + GUILayout.Label("1. Add a dummy Swift file to Xcode project."); + GUILayout.Label("2. Enable 'CLANG_ENABLE_MODULES' build settings and set " + + "'SWIFT_VERSION' to the value below."); + + if (settings.swiftFrameworkSupportWorkaroundEnabled) { + GUILayout.BeginHorizontal(); + GUILayout.Label("Swift Framework Version", + EditorStyles.boldLabel); + settings.swiftLanguageVersion = + EditorGUILayout.TextField(settings.swiftLanguageVersion); + GUILayout.EndHorizontal(); + GUILayout.Label("Used to override 'SWIFT_VERSION' build setting in Xcode. " + + "Leave it blank to prevent override."); + } + } + + GUILayout.Box("", GUILayout.ExpandWidth(true), GUILayout.Height(1)); + } + + settings.analyticsSettings.RenderGui(); + GUILayout.BeginHorizontal(); GUILayout.Label("Verbose Logging", EditorStyles.boldLabel); settings.verboseLoggingEnabled = EditorGUILayout.Toggle(settings.verboseLoggingEnabled); @@ -183,22 +303,70 @@ public void OnGUI() { settings.useProjectSettings = EditorGUILayout.Toggle(settings.useProjectSettings); GUILayout.EndHorizontal(); - GUILayout.Space(10); + EditorGUILayout.EndScrollView(); + GUILayout.EndVertical(); + GUILayout.BeginVertical(); + GUILayout.Space(10); if (GUILayout.Button("Reset to Defaults")) { // Load default settings into the dialog but preserve the state in the user's // saved preferences. var backupSettings = new Settings(); IOSResolver.RestoreDefaultSettings(); + IOSResolver.analytics.Report("settings/reset", "Settings Reset"); LoadSettings(); backupSettings.Save(); } GUILayout.BeginHorizontal(); bool closeWindow = GUILayout.Button("Cancel"); + if (closeWindow) IOSResolver.analytics.Report("settings/cancel", "Settings Cancel"); bool ok = GUILayout.Button("OK"); closeWindow |= ok; - if (ok) settings.Save(); + if (ok) { + IOSResolver.analytics.Report( + "settings/save", + new KeyValuePair[] { + new KeyValuePair( + "podfileGenerationEnabled", + IOSResolver.PodfileGenerationEnabled.ToString()), + new KeyValuePair( + "podToolExecutionViaShellEnabled", + IOSResolver.PodToolExecutionViaShellEnabled.ToString()), + new KeyValuePair( + "podToolShellExecutionSetLang", + IOSResolver.PodToolShellExecutionSetLang.ToString()), + new KeyValuePair( + "autoPodToolInstallInEditorEnabled", + IOSResolver.AutoPodToolInstallInEditorEnabled.ToString()), + new KeyValuePair( + "verboseLoggingEnabled", + IOSResolver.VerboseLoggingEnabled.ToString()), + new KeyValuePair( + "cocoapodsIntegrationMethod", + IOSResolver.CocoapodsIntegrationMethodPref.ToString()), + new KeyValuePair( + "podfileAddUseFrameworks", + IOSResolver.PodfileAddUseFrameworks.ToString()), + new KeyValuePair( + "podfileStaticLinkFrameworks", + IOSResolver.PodfileStaticLinkFrameworks.ToString()), + new KeyValuePair( + "podfileAlwaysAddMainTarget", + IOSResolver.PodfileAlwaysAddMainTarget.ToString()), + new KeyValuePair( + "podfileAllowPodsInMultipleTargets", + IOSResolver.PodfileAllowPodsInMultipleTargets.ToString()), + new KeyValuePair( + "swiftFrameworkSupportWorkaroundEnabled", + IOSResolver.SwiftFrameworkSupportWorkaroundEnabled.ToString()), + new KeyValuePair( + "swiftLanguageVersion", + IOSResolver.SwiftLanguageVersion.ToString()), + }, + "Settings Save"); + settings.Save(); + } if (closeWindow) Close(); GUILayout.EndHorizontal(); GUILayout.EndVertical(); diff --git a/source/IOSResolver/src/VersionNumber.cs b/source/IOSResolver/src/VersionNumber.cs index ff2ef566..c9f3afa8 100644 --- a/source/IOSResolver/src/VersionNumber.cs +++ b/source/IOSResolver/src/VersionNumber.cs @@ -27,7 +27,7 @@ public class IOSResolverVersionNumber { /// /// Version number, patched by the build process. /// - private const string VERSION_STRING = "1.2.111.0"; + private const string VERSION_STRING = "1.2.186"; /// /// Cached version structure. diff --git a/source/ImportUnityPackage/__pycache__/import_unity_package.cpython-311.pyc b/source/ImportUnityPackage/__pycache__/import_unity_package.cpython-311.pyc new file mode 100644 index 00000000..f1c36f67 Binary files /dev/null and b/source/ImportUnityPackage/__pycache__/import_unity_package.cpython-311.pyc differ diff --git a/source/ImportUnityPackage/__pycache__/import_unity_package.cpython-39.pyc b/source/ImportUnityPackage/__pycache__/import_unity_package.cpython-39.pyc new file mode 100644 index 00000000..0d771aa0 Binary files /dev/null and b/source/ImportUnityPackage/__pycache__/import_unity_package.cpython-39.pyc differ diff --git a/source/ImportUnityPackage/import_unity_package.py b/source/ImportUnityPackage/import_unity_package.py new file mode 100755 index 00000000..5ecc17a2 --- /dev/null +++ b/source/ImportUnityPackage/import_unity_package.py @@ -0,0 +1,163 @@ +#!/usr/bin/python +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +r"""A script to import a .unitypackage into a project without Unity. + +Example usage: + import_unity_package.py --projects=path/to/unity/project \ + --packages=mypackage.unitypackage +""" + +import os +import shutil +import tarfile +import tempfile +from absl import app +from absl import flags +from absl import logging + +FLAGS = flags.FLAGS + +flags.DEFINE_multi_string( + "projects", None, "Paths to Unity project directories to unpack packages " + "into. This should be the directory that contains the Assets directory, " + "i.e my/project not my/project/Assets") +flags.DEFINE_multi_string( + "packages", None, "Set of packages to unpack into a project. Packages are " + "unpacked in the order they're specified.") + + +def files_exist(paths_to_check): + """Determine whether the specified files exist. + + Args: + paths_to_check: List of files to check whether they exist. + + Returns: + List of files that do not exist. + """ + return [p for p in paths_to_check if not os.path.isfile(os.path.realpath(p))] + + +def directories_exist(paths_to_check): + """Determine whether the specified directories exist. + + Args: + paths_to_check: List of directories to check whether they exist. + + Returns: + List of directories that do not exist. + """ + return [p for p in paths_to_check if not os.path.isdir(os.path.realpath(p))] + + +def unpack_to_directory(directory, packages): + """Unpack a set of .unitypackage files to a directory. + + Args: + directory: Directory to unpack into. + packages: List of .unitypackage filesname to unpack. + + Returns: + Dictionary containing a list of files that could not be extracted, keyed by + package archive filename. + """ + ignored_files_by_package = {} + for unitypackage in packages: + with tarfile.open(unitypackage) as unitypackage_file: + member_names = unitypackage_file.getnames() + guid_to_path = {} + extracted_files = set() + + # Map each asset GUID to an extract path the path of each extracted asset. + for filename in member_names: + if os.path.basename(filename) == "pathname": + guid = os.path.dirname(filename) + with unitypackage_file.extractfile(filename) as pathname_file: + pathname = pathname_file.read().decode("utf8").strip() + if guid and pathname: + extracted_files.add(filename) + guid_to_path[guid] = pathname + + # Extract each asset to the appropriate path in the output directory. + for filename in member_names: + basename = os.path.basename(filename) + if basename == "asset" or basename == "asset.meta": + guid = os.path.dirname(filename) + if guid: + pathname = guid_to_path.get(guid) + if pathname: + with unitypackage_file.extractfile(filename) as member_file: + extension = os.path.splitext(basename)[1] + output_filename = os.path.join(directory, pathname + extension) + os.makedirs(os.path.dirname(output_filename), exist_ok=True) + with open(output_filename, "wb") as output_file: + shutil.copyfileobj(member_file, output_file) + extracted_files.add(filename) + + # Returns the list of files that could not be extracted in the archive's + # order. + ignored_files = [] + for member in member_names: + if member not in extracted_files: + if unitypackage_file.getmember(member).isfile(): + ignored_files.append(member) + if ignored_files: + ignored_files_by_package[unitypackage] = ignored_files + return ignored_files_by_package + + +def main(unused_argv): + """Unpacks a set of .unitypackage files into a set of Unity projects. + + Args: + unused_argv: Not used. + + Returns: + 0 if successful, 1 otherwise. + """ + # Make sure all input files and output directories exist. + missing_packages = files_exist(FLAGS.packages) + missing_projects = directories_exist(FLAGS.projects) + if missing_packages: + logging.error("Specified packages %s not found.", missing_packages) + if missing_projects: + logging.error("Specified projects %s not found.", missing_projects) + if missing_packages or missing_projects: + return 1 + + with tempfile.TemporaryDirectory() as unpack_directory: + # Unpack all packages into a single directory. + for package, files in unpack_to_directory(unpack_directory, FLAGS.packages): + logging.error("Failed to unpack files %s from package %s", package, files) + + # Copy unpacked packages into each project. + for project in FLAGS.projects: + for dirname, _, filenames in os.walk(unpack_directory): + for filename in filenames: + source_filename = os.path.join(dirname, filename) + relative_filename = source_filename[len(unpack_directory) + 1:] + if os.path.isfile(source_filename): + target_filename = os.path.join(project, relative_filename) + os.makedirs(os.path.dirname(target_filename), exist_ok=True) + shutil.copyfile(source_filename, target_filename) + return 0 + + +if __name__ == "__main__": + flags.mark_flag_as_required("projects") + flags.mark_flag_as_required("packages") + app.run(main) diff --git a/source/ImportUnityPackage/import_unity_package_test.py b/source/ImportUnityPackage/import_unity_package_test.py new file mode 100755 index 00000000..146cf258 --- /dev/null +++ b/source/ImportUnityPackage/import_unity_package_test.py @@ -0,0 +1,133 @@ +#!/usr/bin/python +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for import_unity_package.py.""" + +import os +import shutil +import sys +from absl import flags +from absl.testing import absltest + +# pylint: disable=C6204 +# pylint: disable=W0403 +sys.path.append(os.path.dirname(__file__)) +import import_unity_package +# pylint: enable=C6204 +# pylint: enable=W0403 + +FLAGS = flags.FLAGS + +# Location of test data. +TEST_DATA_PATH = os.path.join(os.path.dirname(__file__), "test_data") + +class ImportUnityPackageTest(absltest.TestCase): + """Test import_unity_package.py.""" + + def setUp(self): + """Create a temporary directory.""" + self.temp_dir = os.path.join(FLAGS.test_tmpdir, "temp") + os.makedirs(self.temp_dir) + + def tearDown(self): + """Clean up the temporary directory.""" + shutil.rmtree(self.temp_dir) + + def test_files_exist(self): + """Test file existance check.""" + non_existent_file = os.path.join(FLAGS.test_tmpdir, "foo/bar.txt") + existent_file = os.path.join(FLAGS.test_tmpdir, "a_file.txt") + with open(existent_file, "wt") as test_file: + test_file.write("hello") + self.assertEqual( + import_unity_package.files_exist([existent_file, non_existent_file]), + [non_existent_file]) + + def test_directories_exist(self): + """Test directory existence check.""" + non_existent_dir = os.path.join(FLAGS.test_tmpdir, "foo/bar") + existent_dir = os.path.join(FLAGS.test_tmpdir, "an/available/dir") + os.makedirs(existent_dir, exist_ok=True) + self.assertEqual( + import_unity_package.directories_exist([non_existent_dir, + existent_dir]), + [non_existent_dir]) + + def read_contents_file(self, test_package_filename): + """Read the contents file for the specified test package. + + Args: + test_package_filename: File to read the expected contents of. + + Returns: + Sorted list of filenames read from + (test_package_filename + ".contents.txt"). + """ + contents = [] + with open(test_package_filename + ".contents.txt", "rt") as contents_file: + return [l.strip() for l in contents_file.readlines() if l.strip()] + + def list_files_in_temp_dir(self): + """List files in the temporary directory. + + Returns: + Sorted list of files relative to the temporary directory. + """ + files = [] + for dirpath, _, filenames in os.walk(self.temp_dir): + for basename in list(filenames): + filename = os.path.join(dirpath, basename) + if os.path.isfile(filename): + files.append(filename[len(self.temp_dir) + 1:]) + return sorted(files) + + def test_unpack_to_directory_valid_archive(self): + """Unpack a valid unitypackage into a directory.""" + packages = [ + os.path.join(TEST_DATA_PATH, + "external-dependency-manager-1.2.144.unitypackage"), + os.path.join(TEST_DATA_PATH, + "external-dependency-manager-1.2.153.unitypackage") + ] + self.assertEqual(import_unity_package.unpack_to_directory(self.temp_dir, + packages), {}) + + expected_files = set(self.read_contents_file(packages[0])) + expected_files = expected_files.union(self.read_contents_file(packages[1])) + + self.maxDiff = None + self.assertEqual(self.list_files_in_temp_dir(), + sorted(expected_files)) + + def test_unpack_to_directory_invalid_archive(self): + """Unpack a broken unitypackage into a directory.""" + # This archive has been modified so that 9b7b6f84d4eb4f549252df73305e17c8 + # does not have a path. + packages = [ + os.path.join( + TEST_DATA_PATH, + "external-dependency-manager-1.2.144-broken.unitypackage") + ] + self.assertEqual( + import_unity_package.unpack_to_directory(self.temp_dir, packages), + {packages[0]: [ + "9b7b6f84d4eb4f549252df73305e17c8/asset.meta", + "9b7b6f84d4eb4f549252df73305e17c8/asset"]}) + + +if __name__ == "__main__": + absltest.main() + diff --git a/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144-broken.unitypackage b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144-broken.unitypackage new file mode 100644 index 00000000..f352cd95 Binary files /dev/null and b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144-broken.unitypackage differ diff --git a/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144.unitypackage b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144.unitypackage new file mode 100644 index 00000000..a940d314 Binary files /dev/null and b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144.unitypackage differ diff --git a/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144.unitypackage.contents.txt b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144.unitypackage.contents.txt new file mode 100644 index 00000000..294949ff --- /dev/null +++ b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.144.unitypackage.contents.txt @@ -0,0 +1,36 @@ +Assets/ExternalDependencyManager.meta +Assets/ExternalDependencyManager/Editor.meta +Assets/ExternalDependencyManager/Editor/CHANGELOG.md +Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.144.dll +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.144.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.144.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.144.dll.meta +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.144.dll +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.144.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.144.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.144.dll.meta +Assets/ExternalDependencyManager/Editor/Google.UnityPackageManagerResolver_v1.2.144.dll +Assets/ExternalDependencyManager/Editor/Google.UnityPackageManagerResolver_v1.2.144.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.UnityPackageManagerResolver_v1.2.144.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.UnityPackageManagerResolver_v1.2.144.dll.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.144.dll +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.144.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.144.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.144.dll.meta +Assets/ExternalDependencyManager/Editor/GoogleRegistries.xml +Assets/ExternalDependencyManager/Editor/GoogleRegistries.xml.meta +Assets/ExternalDependencyManager/Editor/LICENSE +Assets/ExternalDependencyManager/Editor/LICENSE.meta +Assets/ExternalDependencyManager/Editor/README.md +Assets/ExternalDependencyManager/Editor/README.md.meta +Assets/ExternalDependencyManager/Editor/external-dependency-manager_v1.2.144.txt +Assets/ExternalDependencyManager/Editor/external-dependency-manager_v1.2.144.txt.meta +Assets/PlayServicesResolver.meta +Assets/PlayServicesResolver/Editor.meta +Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt +Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta diff --git a/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.153.unitypackage b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.153.unitypackage new file mode 100644 index 00000000..8e466704 Binary files /dev/null and b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.153.unitypackage differ diff --git a/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.153.unitypackage.contents.txt b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.153.unitypackage.contents.txt new file mode 100644 index 00000000..289574ea --- /dev/null +++ b/source/ImportUnityPackage/test_data/external-dependency-manager-1.2.153.unitypackage.contents.txt @@ -0,0 +1,32 @@ +Assets/ExternalDependencyManager/Editor/CHANGELOG.md +Assets/ExternalDependencyManager/Editor/CHANGELOG.md.meta +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.153.dll +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.153.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.153.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.IOSResolver_v1.2.153.dll.meta +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.153.dll +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.153.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.153.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.JarResolver_v1.2.153.dll.meta +Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver_v1.2.153.dll +Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver_v1.2.153.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver_v1.2.153.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.PackageManagerResolver_v1.2.153.dll.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.153.dll +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.153.dll.mdb +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.153.dll.mdb.meta +Assets/ExternalDependencyManager/Editor/Google.VersionHandlerImpl_v1.2.153.dll.meta +Assets/ExternalDependencyManager/Editor/GoogleRegistries.xml +Assets/ExternalDependencyManager/Editor/GoogleRegistries.xml.meta +Assets/ExternalDependencyManager/Editor/LICENSE +Assets/ExternalDependencyManager/Editor/LICENSE.meta +Assets/ExternalDependencyManager/Editor/README.md +Assets/ExternalDependencyManager/Editor/README.md.meta +Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.153_manifest.txt +Assets/ExternalDependencyManager/Editor/external-dependency-manager_version-1.2.153_manifest.txt.meta +Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt +Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta diff --git a/source/PackageManager/PackageManager.csproj b/source/IntegrationTester/IntegrationTester.csproj similarity index 80% rename from source/PackageManager/PackageManager.csproj rename to source/IntegrationTester/IntegrationTester.csproj index 27ffa43b..6faa768d 100644 --- a/source/PackageManager/PackageManager.csproj +++ b/source/IntegrationTester/IntegrationTester.csproj @@ -5,10 +5,10 @@ AnyCPU 8.0.30703 2.0 - {8B0A2564-01ED-426B-AF33-33EED4A81828} + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040} Library Google - Google.PackageManager + Google.IntegrationTester 1.2 v3.5 @@ -23,7 +23,8 @@ False - none + True + full True bin\Release DEBUG;UNITY_EDITOR @@ -51,21 +52,19 @@ + + + - - - - - - {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + {5378B37A-887E-49ED-A8AE-42FA843AA9DC} VersionHandler - - {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A} - PlayServicesResolver + + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + VersionHandlerImpl diff --git a/source/PackageManager/Properties/AssemblyInfo.cs b/source/IntegrationTester/Properties/AssemblyInfo.cs similarity index 87% rename from source/PackageManager/Properties/AssemblyInfo.cs rename to source/IntegrationTester/Properties/AssemblyInfo.cs index 23b4e109..d2f2e8b1 100644 --- a/source/PackageManager/Properties/AssemblyInfo.cs +++ b/source/IntegrationTester/Properties/AssemblyInfo.cs @@ -1,5 +1,5 @@ -// -// Copyright (C) 2014 Google Inc. All Rights Reserved. +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ // Information about this assembly is defined by the following attributes. // Change them to the values specific to your project. -[assembly: AssemblyTitle("Google.PackageManager")] +[assembly: AssemblyTitle("Google.IntegrationTester")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Google Inc.")] @@ -37,7 +37,7 @@ // if desired. See the Mono documentation for more information about signing. // [assembly: AssemblyDelaySign(false)] -// // +// // // Copyright (C) 2016 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,4 +52,5 @@ // See the License for the specific language governing permissions and // limitations under the License. // [assembly: AssemblyKeyFile("")] -[assembly: InternalsVisibleToAttribute("ControllerTests")] \ No newline at end of file + +[assembly: InternalsVisibleTo("Google.AndroidResolverTests")] diff --git a/source/IntegrationTester/src/Runner.cs b/source/IntegrationTester/src/Runner.cs new file mode 100644 index 00000000..a7e8c9a9 --- /dev/null +++ b/source/IntegrationTester/src/Runner.cs @@ -0,0 +1,445 @@ +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Xml; + +using Google; + +namespace Google.IntegrationTester { + + /// + /// Should be applied to "static void Method()" methods that will be called when the Runner is + /// ready to be initialized with test cases. + /// + public class InitializerAttribute : Attribute {} + + /// + /// Can be applied to static methods that conform to TestCase.MethodDelegate which will add the + /// methods to the set of tests cases to execute. + /// + public class TestCaseAttribute : Attribute {} + + /// + /// Runs a series of asynchronous test cases in the Unity Editor. + /// + [UnityEditor.InitializeOnLoad] + public static class Runner { + + /// + /// Executed test case names and failure messages (if any). + /// + private static List testCaseResults = new List(); + + /// + /// Set of test cases to execute. + /// + private static List testCases = new List(); + + /// + /// Backing store for UnityVersion. + /// + private static float unityVersion; + + /// + /// Get the current Unity version. + /// + public static float UnityVersion { get { return unityVersion; } } + + /// + /// Whether the default initializer was called. + /// + private static bool defaultInitializerCalled = false; + + /// + /// Whether the default test case was called. + /// + private static bool defaultTestCaseCalled = false; + + /// + /// File to store snapshot of test case results. + /// + private static string TestCaseResultsFilename = "Temp/GvhRunnerTestCaseResults.xml"; + + /// + /// Register a method to call when the Version Handler has enabled all plugins in the + /// project. + /// + static Runner() { + // Disable stack traces for more condensed logs. + try { + foreach (var logType in + new [] { UnityEngine.LogType.Log, UnityEngine.LogType.Warning }) { + // Unity 2017 and above have the Application.SetStackTraceLogType to configure + // stack traces per log level. + VersionHandler.InvokeStaticMethod( + typeof(UnityEngine.Application), "SetStackTraceLogType", + new object[] { logType, UnityEngine.StackTraceLogType.None }); + } + } catch (Exception) { + // Fallback to the legacy method. + UnityEngine.Application.stackTraceLogType = UnityEngine.StackTraceLogType.None; + } + + UnityEngine.Debug.Log("Set up callback on Version Handler completion."); + Google.VersionHandler.UpdateCompleteMethods = new [] { + "Google.IntegrationTester:Google.IntegrationTester.Runner:VersionHandlerReady" + }; + UnityEngine.Debug.Log("Enable plugin using the Version Handler."); + Google.VersionHandler.UpdateNow(); + } + + /// + /// Add a set of test cases to the list to be executed. + /// + /// Test cases to add to the list to execute. + public static void ScheduleTestCases(IEnumerable tests) { + testCases.AddRange(tests); + } + + /// + /// Add a single test case to the list to be executed. + /// + /// Test case to add to the list to execute. + public static void ScheduleTestCase(TestCase test) { + testCases.Add(test); + } + + /// + /// Called when the Version Handler has enabled all managed plugins in a project. + /// + public static void VersionHandlerReady() { + UnityEngine.Debug.Log("VersionHandler is ready."); + Google.VersionHandler.UpdateCompleteMethods = null; + // Start executing tests. + ConfigureTestCases(); + RunOnMainThread.Run(() => { ExecuteNextTestCase(); }, runNow: false); + } + + /// + /// Configure tests to run. + /// + /// + /// Finds and calls all initialization methods with the InitializerAttribute. + /// + private static void ConfigureTestCases() { + unityVersion = Google.VersionHandler.GetUnityVersionMajorMinor(); + + // Gather test initializers and test case methods. + var initializerMethods = new List(); + var testCaseMethods = new List(); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { + foreach (var type in assembly.GetTypes()) { + IEnumerable methods; + try { + methods = type.GetMethods(); + } catch (Exception) { + // TargetInvocationException, TypeLoadException and others can be thrown + // when retrieving the methods of some .NET assemblies + // (e.g System.Web.UI.WebControls.ModelDataSourceView) so ignore them. + continue; + } + foreach (var method in methods) { + foreach (var attribute in method.GetCustomAttributes(true)) { + if (attribute is InitializerAttribute) { + initializerMethods.Add(method); + break; + } else if (attribute is TestCaseAttribute) { + testCaseMethods.Add(method); + break; + } + } + } + } + } + + bool initializationSuccessful = true; + foreach (var initializer in initializerMethods) { + try { + initializer.Invoke(null, null); + } catch (Exception e) { + UnityEngine.Debug.Log(String.Format("FAILED: Unable to initialize {0} ({1})", + initializer.Name, e)); + initializationSuccessful = false; + } + } + + // Try adding test cases to the list to execute. + foreach (var testCaseMethod in testCaseMethods) { + try { + var testCaseMethodDelegate = (TestCase.MethodDelegate)Delegate.CreateDelegate( + typeof(TestCase.MethodDelegate), null, testCaseMethod, true); + ScheduleTestCase(new TestCase() { + Name = testCaseMethod.Name, + Method = testCaseMethodDelegate + }); + } catch (Exception e) { + UnityEngine.Debug.Log(String.Format( + "FAILED: Test case {0} does not implement TestCase.MethodDelegate ({1})", + testCaseMethod.Name, e)); + initializationSuccessful = false; + } + } + + // Restore for all executed test cases, restore results and remove all pending test + // cases that are complete. + var executedTestCaseNames = new HashSet(); + foreach (var executedTestCase in ReadTestCaseResults()) { + testCaseResults.Add(executedTestCase); + executedTestCaseNames.Add(executedTestCase.TestCaseName); + } + var filteredTestCases = new List(); + foreach (var testCase in testCases) { + if (!executedTestCaseNames.Contains(testCase.Name)) { + filteredTestCases.Add(testCase); + } + } + defaultTestCaseCalled = executedTestCaseNames.Contains("DefaultTestCase"); + testCases = filteredTestCases; + + if (!defaultInitializerCalled) { + UnityEngine.Debug.Log("FAILED: Default Initializer not called."); + initializationSuccessful = false; + } + + if (!initializationSuccessful) Exit(false); + } + + /// + /// Default initializer to test the Initializer attribute. + /// + [Initializer] + public static void DefaultInitializer() { + defaultInitializerCalled = true; + } + + /// + /// Default test case to test the TestCase attribute. + /// + /// Object executing this method. + /// Called when the test case is complete. + [TestCase] + public static void DefaultTestCase(TestCase testCase, + Action testCaseComplete) { + defaultTestCaseCalled = true; + testCaseComplete(new TestCaseResult(testCase)); + } + + /// + /// Exit the application if the -gvh_noexitontestcompletion command line flag isn't set. + /// + /// Whether the tests passed. + private static void Exit(bool passed) { + if (!Environment.CommandLine.ToLower().Contains("-gvh_noexitontestcompletion")) { + UnityEditor.EditorApplication.Exit(passed ? 0 : 1); + } + } + + /// + /// Log test result summary and quit the application. + /// + public static void LogSummaryAndExit() { + bool passed = true; + var testSummaryLines = new List(); + if (!defaultTestCaseCalled) { + testSummaryLines.Add("Default test case not called"); + passed = false; + } + foreach (var testCaseResult in testCaseResults) { + testSummaryLines.Add(testCaseResult.FormatString(false)); + passed &= testCaseResult.Succeeded; + } + UnityEngine.Debug.Log(String.Format("Test(s) {0}.\n{1}", passed ? "PASSED" : "FAILED", + String.Join("\n", testSummaryLines.ToArray()))); + Exit(passed); + } + + /// + /// Read test case results from the journal. + /// + /// List of TestCaseResults. + private static List ReadTestCaseResults() { + var readTestCaseResults = new List(); + if (!File.Exists(TestCaseResultsFilename)) return readTestCaseResults; + + bool successful = XmlUtilities.ParseXmlTextFileElements( + TestCaseResultsFilename, new Logger(), + (XmlTextReader reader, string elementName, bool isStart, string parentElementName, + List elementNameStack) => { + TestCaseResult currentTestCaseResult = null; + int testCaseResultsCount = readTestCaseResults.Count; + if (testCaseResultsCount > 0) { + currentTestCaseResult = readTestCaseResults[testCaseResultsCount - 1]; + } + if (elementName == "TestCaseResults" && parentElementName == "") { + if (isStart) { + readTestCaseResults.Clear(); + } + return true; + } else if (elementName == "TestCaseResult" && + parentElementName == "TestCaseResults") { + if (isStart) { + readTestCaseResults.Add(new TestCaseResult(new TestCase())); + } + return true; + } else if (elementName == "TestCaseName" && + parentElementName == "TestCaseResult") { + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + currentTestCaseResult.TestCaseName = reader.ReadContentAsString(); + } + return true; + } else if (elementName == "Skipped" && parentElementName == "TestCaseResult") { + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + currentTestCaseResult.Skipped = reader.ReadContentAsBoolean(); + } + return true; + } else if (elementName == "ErrorMessages" && + parentElementName == "TestCaseResult") { + return true; + } else if (elementName == "ErrorMessage" && + parentElementName == "ErrorMessages") { + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + currentTestCaseResult.ErrorMessages.Add(reader.ReadContentAsString()); + } + return true; + } + return false; + }); + if (!successful) { + UnityEngine.Debug.LogWarning( + String.Format("Failed while reading {0}, test execution will restart if the " + + "app domain is reloaded.", TestCaseResultsFilename)); + } + return readTestCaseResults; + } + + /// + /// Log a test case result to the journal so that it isn't executed again if the app + /// domain is reloaded. + /// + private static bool WriteTestCaseResult(TestCaseResult testCaseResult) { + var existingTestCaseResults = ReadTestCaseResults(); + existingTestCaseResults.Add(testCaseResult); + try { + Directory.CreateDirectory(Path.GetDirectoryName(TestCaseResultsFilename)); + using (var writer = new XmlTextWriter(new StreamWriter(TestCaseResultsFilename)) { + Formatting = Formatting.Indented + }) { + writer.WriteStartElement("TestCaseResults"); + foreach (var result in existingTestCaseResults) { + writer.WriteStartElement("TestCaseResult"); + if (!String.IsNullOrEmpty(result.TestCaseName)) { + writer.WriteStartElement("TestCaseName"); + writer.WriteValue(result.TestCaseName); + writer.WriteEndElement(); + } + writer.WriteStartElement("Skipped"); + writer.WriteValue(result.Skipped); + writer.WriteEndElement(); + if (result.ErrorMessages.Count > 0) { + writer.WriteStartElement("ErrorMessages"); + foreach (var errorMessage in result.ErrorMessages) { + writer.WriteStartElement("ErrorMessage"); + writer.WriteValue(errorMessage); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + writer.WriteEndElement(); + writer.Flush(); + writer.Close(); + } + } catch (Exception e) { + UnityEngine.Debug.LogWarning( + String.Format("Failed while writing {0} ({1}), test execution will restart " + + "if the app domain is reloaded.", TestCaseResultsFilename, e)); + return false; + } + return true; + } + + /// + /// Log a test case result with error details. + /// + /// Result to log. + public static void LogTestCaseResult(TestCaseResult testCaseResult) { + testCaseResults.Add(testCaseResult); + UnityEngine.Debug.Log(testCaseResult.FormatString(true)); + WriteTestCaseResult(testCaseResult); + } + + /// + /// Execute a function for a test case catching any exceptions and logging the result. + /// + /// Object executing this method. + /// Action to execute. + /// Whether to execute the next test case if the specified action + /// fails. + /// true if the action executed without any exceptions, false otherwise. + public static bool ExecuteTestCase(TestCase testCase, Action testCaseAction, + bool executeNext) { + bool succeeded = true; + try { + testCaseAction(); + } catch (Exception e) { + LogTestCaseResult(new TestCaseResult(testCase) { + ErrorMessages = new List { e.ToString() } + }); + succeeded = false; + } + if (!succeeded && executeNext) { + RunOnMainThread.Run(() => { ExecuteNextTestCase(); }); + } + return succeeded; + } + + /// + /// Execute the next queued test case. + /// + private static void ExecuteNextTestCase() { + bool executeNext; + do { + executeNext = false; + UnityEngine.Debug.Log(String.Format("Remaining test cases {0}", testCases.Count)); + if (testCases.Count > 0) { + var testCase = testCases[0]; + testCases.RemoveAt(0); + UnityEngine.Debug.Log(String.Format("Test {0} starting...", testCase.Name)); + // If the test threw an exception on this thread, execute the next test case + // in a loop. + executeNext = !ExecuteTestCase( + testCase, + () => { + testCase.Method(testCase, (testCaseResult) => { + UnityEngine.Debug.Log(String.Format("Test {0} complete", + testCase.Name)); + testCaseResult.TestCaseName = testCase.Name; + LogTestCaseResult(testCaseResult); + RunOnMainThread.Run(() => { ExecuteNextTestCase(); }); + }); + }, false); + } else { + LogSummaryAndExit(); + } + } while (executeNext); + } + } +} diff --git a/source/IntegrationTester/src/TestCase.cs b/source/IntegrationTester/src/TestCase.cs new file mode 100644 index 00000000..2d87fa4d --- /dev/null +++ b/source/IntegrationTester/src/TestCase.cs @@ -0,0 +1,47 @@ +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; + +namespace Google.IntegrationTester { + + /// + /// Test case class. + /// + /// This specifies a test case to execute. Each test case has a name which is used to + /// log the result of the test and the method to execute as part of the test case. + /// + public class TestCase { + + /// + /// Test case delegate. + /// + /// Object executing this method. + /// Called when the test case is complete. + public delegate void MethodDelegate(TestCase testCase, + Action testCaseComplete); + + /// + /// Name of the test case. + /// + public string Name { get; set; } + + /// + /// Delegate that runs the test case logic. + /// + public MethodDelegate Method { get; set; } + } +} diff --git a/source/IntegrationTester/src/TestCaseResult.cs b/source/IntegrationTester/src/TestCaseResult.cs new file mode 100644 index 00000000..461b9c2b --- /dev/null +++ b/source/IntegrationTester/src/TestCaseResult.cs @@ -0,0 +1,72 @@ +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; + +namespace Google.IntegrationTester { + + /// + /// Result of a test. + /// + public class TestCaseResult { + + /// + /// Initialize the class. + /// + public TestCaseResult(TestCase testCase) { + TestCaseName = testCase.Name; + ErrorMessages = new List(); + Skipped = false; + } + + /// + /// Name of the test case. This does not need to be set by the test case. + /// + public string TestCaseName { get; set; } + + /// + /// Error messages reported by a test case failure. + /// + public List ErrorMessages { get; set; } + + /// + /// Whether the test case was skipped. + /// + public bool Skipped { get; set; } + + /// + /// Whether the test case succeeded. + /// + public bool Succeeded { + get { + return Skipped || ErrorMessages == null || ErrorMessages.Count == 0; + } + } + + /// + /// Format the result as a string. + /// + /// Include failure messages in the list. + public string FormatString(bool includeFailureMessages) { + return String.Format("Test {0}: {1}{2}", TestCaseName, + Skipped ? "SKIPPED" : Succeeded ? "PASSED" : "FAILED", + includeFailureMessages && ErrorMessages != null && + ErrorMessages.Count > 0 ? + "\n" + String.Join("\n", ErrorMessages.ToArray()) : ""); + } + } +} diff --git a/source/JarResolver.sln b/source/JarResolver.sln deleted file mode 100644 index c4ce3495..00000000 --- a/source/JarResolver.sln +++ /dev/null @@ -1,239 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JarResolverLib", "JarResolverLib\JarResolverLib.csproj", "{CC4F239D-3C7F-4164-830F-9215AE15B32A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JarResolverTests", "JarResolverTests\JarResolverTests.csproj", "{593254D7-6358-40A6-B0C8-F0616BBF499D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlayServicesResolver", "PlayServicesResolver\PlayServicesResolver.csproj", "{82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionHandler", "VersionHandler\VersionHandler.csproj", "{5378B37A-887E-49ED-A8AE-42FA843AA9DC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VersionHandlerImpl", "VersionHandlerImpl\VersionHandlerImpl.csproj", "{1E162334-8EA2-440A-9B3A-13FD8FE5C22E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IOSResolver", "IOSResolver\IOSResolver.csproj", "{5B581BAE-D432-41AB-AEED-FD269AEA081D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageManager", "PackageManager\PackageManager.csproj", "{8B0A2564-01ED-426B-AF33-33EED4A81828}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageManagerTests", "PackageManagerTests\PackageManagerTests.csproj", "{7E1CDCE1-1B39-48F6-9DEA-A714FD6654D2}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {593254D7-6358-40A6-B0C8-F0616BBF499D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {593254D7-6358-40A6-B0C8-F0616BBF499D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {593254D7-6358-40A6-B0C8-F0616BBF499D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {593254D7-6358-40A6-B0C8-F0616BBF499D}.Release|Any CPU.Build.0 = Release|Any CPU - {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A}.Release|Any CPU.Build.0 = Release|Any CPU - {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CC4F239D-3C7F-4164-830F-9215AE15B32A}.Release|Any CPU.Build.0 = Release|Any CPU - {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5378B37A-887E-49ED-A8AE-42FA843AA9DC}.Release|Any CPU.Build.0 = Release|Any CPU - {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E162334-8EA2-440A-9B3A-13FD8FE5C22E}.Release|Any CPU.Build.0 = Release|Any CPU - {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5B581BAE-D432-41AB-AEED-FD269AEA081D}.Release|Any CPU.Build.0 = Release|Any CPU - {8B0A2564-01ED-426B-AF33-33EED4A81828}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8B0A2564-01ED-426B-AF33-33EED4A81828}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B0A2564-01ED-426B-AF33-33EED4A81828}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8B0A2564-01ED-426B-AF33-33EED4A81828}.Release|Any CPU.Build.0 = Release|Any CPU - {7E1CDCE1-1B39-48F6-9DEA-A714FD6654D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7E1CDCE1-1B39-48F6-9DEA-A714FD6654D2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7E1CDCE1-1B39-48F6-9DEA-A714FD6654D2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7E1CDCE1-1B39-48F6-9DEA-A714FD6654D2}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = PlayServicesResolver\PlayServicesResolver.csproj - Policies = $0 - $0.DotNetNamingPolicy = $1 - $1.DirectoryNamespaceAssociation = Hierarchical - $1.ResourceNamePolicy = FileFormatDefault - $0.TextStylePolicy = $2 - $2.inheritsSet = VisualStudio - $2.inheritsScope = text/plain - $2.scope = text/plain - $2.FileWidth = 100 - $0.CSharpFormattingPolicy = $3 - $3.IndentSwitchBody = True - $3.AnonymousMethodBraceStyle = NextLine - $3.PropertyBraceStyle = NextLine - $3.PropertyGetBraceStyle = NextLine - $3.PropertySetBraceStyle = NextLine - $3.EventBraceStyle = NextLine - $3.EventAddBraceStyle = NextLine - $3.EventRemoveBraceStyle = NextLine - $3.StatementBraceStyle = NextLine - $3.ElseNewLinePlacement = NewLine - $3.CatchNewLinePlacement = NewLine - $3.FinallyNewLinePlacement = NewLine - $3.WhileNewLinePlacement = DoNotCare - $3.ArrayInitializerWrapping = DoNotChange - $3.ArrayInitializerBraceStyle = NextLine - $3.BeforeMethodDeclarationParentheses = False - $3.BeforeMethodCallParentheses = False - $3.BeforeConstructorDeclarationParentheses = False - $3.BeforeDelegateDeclarationParentheses = False - $3.NewParentheses = False - $3.SpacesBeforeBrackets = False - $3.inheritsSet = Mono - $3.inheritsScope = text/x-csharp - $3.scope = text/x-csharp - $3.NewLinesForBracesInTypes = False - $3.NewLinesForBracesInMethods = False - $3.SpacingAfterMethodDeclarationName = False - $3.SpaceAfterMethodCallName = False - $3.SpaceBeforeOpenSquareBracket = False - $0.TextStylePolicy = $4 - $4.inheritsSet = VisualStudio - $4.inheritsScope = text/plain - $4.scope = text/plain - $0.StandardHeader = $5 - $5.Text = @\r\n Copyright ${Year} ${CopyrightHolder}\n\n Licensed under the Apache License, Version 2.0 (the "License");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an "AS IS" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License. - $5.IncludeInNewFiles = True - $0.NameConventionPolicy = $6 - $6.Rules = $7 - $7.NamingRule = $8 - $8.Name = Type Parameters - $8.AffectedEntity = TypeParameter - $8.VisibilityMask = VisibilityMask - $8.NamingStyle = PascalCase - $8.IncludeInstanceMembers = True - $8.IncludeStaticEntities = True - $8.RequiredPrefixes = $30 - $30.String = T - $8.RequiredSuffixes = $31 - $31.String = Exception - $7.NamingRule = $9 - $9.Name = Types - $9.AffectedEntity = Class, Struct, Enum, Delegate - $9.VisibilityMask = Public - $9.NamingStyle = PascalCase - $9.IncludeInstanceMembers = True - $9.IncludeStaticEntities = True - $7.NamingRule = $10 - $10.Name = Interfaces - $10.RequiredPrefixes = $11 - $11.String = I - $10.AffectedEntity = Interface - $10.VisibilityMask = Public - $10.NamingStyle = PascalCase - $10.IncludeInstanceMembers = True - $10.IncludeStaticEntities = True - $7.NamingRule = $12 - $12.Name = Attributes - $12.RequiredSuffixes = $13 - $13.String = Attribute - $12.AffectedEntity = CustomAttributes - $12.VisibilityMask = Public - $12.NamingStyle = PascalCase - $12.IncludeInstanceMembers = True - $12.IncludeStaticEntities = True - $7.NamingRule = $14 - $14.Name = Event Arguments - $14.RequiredSuffixes = $15 - $15.String = EventArgs - $14.AffectedEntity = CustomEventArgs - $14.VisibilityMask = Public - $14.NamingStyle = PascalCase - $14.IncludeInstanceMembers = True - $14.IncludeStaticEntities = True - $7.NamingRule = $16 - $16.Name = Exceptions - $16.RequiredSuffixes = $17 - $17.String = Exception - $16.AffectedEntity = CustomExceptions - $16.VisibilityMask = VisibilityMask - $16.NamingStyle = PascalCase - $16.IncludeInstanceMembers = True - $16.IncludeStaticEntities = True - $7.NamingRule = $18 - $18.Name = Methods - $18.AffectedEntity = Methods - $18.VisibilityMask = Protected, Public - $18.NamingStyle = PascalCase - $18.IncludeInstanceMembers = True - $18.IncludeStaticEntities = True - $7.NamingRule = $19 - $19.Name = Static Readonly Fields - $19.AffectedEntity = ReadonlyField - $19.VisibilityMask = Protected, Public - $19.NamingStyle = PascalCase - $19.IncludeInstanceMembers = False - $19.IncludeStaticEntities = True - $7.NamingRule = $20 - $20.Name = Fields - $20.AffectedEntity = Field - $20.VisibilityMask = Protected, Public - $20.NamingStyle = PascalCase - $20.IncludeInstanceMembers = True - $20.IncludeStaticEntities = True - $7.NamingRule = $21 - $21.Name = ReadOnly Fields - $21.AffectedEntity = ReadonlyField - $21.VisibilityMask = Protected, Public - $21.NamingStyle = PascalCase - $21.IncludeInstanceMembers = True - $21.IncludeStaticEntities = False - $7.NamingRule = $22 - $22.Name = Constant Fields - $22.AffectedEntity = ConstantField - $22.VisibilityMask = Protected, Public - $22.NamingStyle = PascalCase - $22.IncludeInstanceMembers = True - $22.IncludeStaticEntities = True - $7.NamingRule = $23 - $23.Name = Properties - $23.AffectedEntity = Property - $23.VisibilityMask = Protected, Public - $23.NamingStyle = PascalCase - $23.IncludeInstanceMembers = True - $23.IncludeStaticEntities = True - $7.NamingRule = $24 - $24.Name = Events - $24.AffectedEntity = Event - $24.VisibilityMask = Protected, Public - $24.NamingStyle = PascalCase - $24.IncludeInstanceMembers = True - $24.IncludeStaticEntities = True - $7.NamingRule = $25 - $25.Name = Enum Members - $25.AffectedEntity = EnumMember - $25.VisibilityMask = VisibilityMask - $25.NamingStyle = PascalCase - $25.IncludeInstanceMembers = True - $25.IncludeStaticEntities = True - $7.NamingRule = $26 - $26.Name = Parameters - $26.AffectedEntity = Parameter - $26.VisibilityMask = VisibilityMask - $26.NamingStyle = CamelCase - $26.IncludeInstanceMembers = True - $26.IncludeStaticEntities = True - $7.NamingRule = $27 - $27.Name = Type Parameters - $27.RequiredPrefixes = $28 - $28.String = T - $27.AffectedEntity = TypeParameter - $27.VisibilityMask = VisibilityMask - $27.NamingStyle = PascalCase - $27.IncludeInstanceMembers = True - $27.IncludeStaticEntities = True - $0.VersionControlPolicy = $29 - $29.inheritsSet = Mono - version = 1.2 - EndGlobalSection -EndGlobal diff --git a/source/JarResolverLib/JarResolverLib.csproj b/source/JarResolverLib/JarResolverLib.csproj index acaa1169..2c43e509 100644 --- a/source/JarResolverLib/JarResolverLib.csproj +++ b/source/JarResolverLib/JarResolverLib.csproj @@ -47,7 +47,10 @@ + + + diff --git a/source/JarResolverLib/src/Google.JarResolver/Dependency.cs b/source/JarResolverLib/src/Google.JarResolver/Dependency.cs index 3462565c..f740e9c5 100644 --- a/source/JarResolverLib/src/Google.JarResolver/Dependency.cs +++ b/source/JarResolverLib/src/Google.JarResolver/Dependency.cs @@ -49,13 +49,15 @@ public class Dependency { /// Group ID /// Artifact ID /// Version constraint. + /// Artifact classifier. /// Android SDK package identifiers required for this /// artifact. /// List of additional repository directories to search for /// this artifact. /// Human readable string that describes where this dependency /// originated. - public Dependency(string group, string artifact, string version, string[] packageIds=null, + public Dependency(string group, string artifact, string version, + string classifier = null, string[] packageIds=null, string[] repositories=null, string createdBy=null) { // If the dependency was added programmatically, strip out stack frames from inside the // library since the developer is likely interested in where in their code the @@ -75,6 +77,12 @@ public Dependency(string group, string artifact, string version, string[] packag continue; } filterFrames = false; + + // From Unity 2019, System.Environment.StackTrace stops returning parentheses. + // Remove the parentheses here to keep result consistent. + if (frameString.EndsWith("()")) { + frameString = frameString.Substring(0, frameString.Length - 2); + } usefulFrames.Add(frameString); } createdBy = String.Join("\n", usefulFrames.ToArray()); @@ -82,6 +90,7 @@ public Dependency(string group, string artifact, string version, string[] packag Group = group; Artifact = artifact; Version = version; + Classifier = classifier; PackageIds = packageIds; Repositories = repositories; CreatedBy = createdBy; @@ -95,6 +104,7 @@ public Dependency(Dependency dependency) { Group = dependency.Group; Artifact = dependency.Artifact; Version = dependency.Version; + Classifier = dependency.Classifier; if (dependency.PackageIds != null) { PackageIds = (string[])dependency.PackageIds.Clone(); } @@ -127,6 +137,14 @@ public Dependency(Dependency dependency) { /// The version. public string Version { get; set; } + /// + /// Gets the classifier. + /// + /// The classifier. + public string Classifier { get; set; } + + /// + /// /// Array of Android SDK identifiers for packages that are required for this /// artifact. @@ -152,7 +170,11 @@ public Dependency(Dependency dependency) { /// group, artifact and version constraint. /// /// The key. - public string Key { get { return Group + ":" + Artifact + ":" + Version; } } + public string Key { get { + string key = Group + ":" + Artifact + ":" + Version; + if (!String.IsNullOrEmpty(Classifier)) + key += ":" + Classifier; + return key; } } /// /// Returns a that represents @@ -171,7 +193,7 @@ public Dependency(Dependency dependency) { private static bool IsGreater(string version1, string version2) { version1 = version1.EndsWith("+") ? version1.Substring(0, version1.Length - 1) : version1; - version2 = version1.EndsWith("+") ? + version2 = version2.EndsWith("+") ? version2.Substring(0, version2.Length - 1) : version2; string[] version1Components = version1.Split('.'); string[] version2Components = version2.Split('.'); diff --git a/source/JarResolverLib/src/Google.JarResolver/PlayServicesSupport.cs b/source/JarResolverLib/src/Google.JarResolver/PlayServicesSupport.cs index d210fa14..673b8915 100644 --- a/source/JarResolverLib/src/Google.JarResolver/PlayServicesSupport.cs +++ b/source/JarResolverLib/src/Google.JarResolver/PlayServicesSupport.cs @@ -246,7 +246,8 @@ private static Dependency AddCommonPackageIds(Dependency dep) { } } if (packageNames.Count > 0) packageIds = packageNames.ToArray(); - return new Dependency(dep.Group, dep.Artifact, dep.Version, packageIds: packageIds, + return new Dependency(dep.Group, dep.Artifact, dep.Version, + classifier: dep.Classifier, packageIds: packageIds, repositories: dep.Repositories); } @@ -275,17 +276,20 @@ private static Dependency AddCommonPackageIds(Dependency dep) { /// Group - the Group Id of the artifact /// Artifact - Artifact Id /// Version - the version constraint + /// Classifier - the artifact classifer. /// Optional list of Android SDK package identifiers. /// List of additional repository directories to search for /// this artifact. /// Human readable string that describes where this dependency /// originated. public void DependOn(string group, string artifact, string version, - string[] packageIds = null, string[] repositories = null, - string createdBy = null) { + string classifier = null, string[] packageIds = null, + string[] repositories = null, string createdBy = null) { Log("DependOn - group: " + group + " artifact: " + artifact + " version: " + version + + " classifier: " + + (classifier!= null ? classifier : "null") + " packageIds: " + (packageIds != null ? String.Join(", ", packageIds) : "null") + " repositories: " + @@ -296,7 +300,8 @@ public void DependOn(string group, string artifact, string version, var depRepoList = new List(repositories); depRepoList.AddRange(repositoryPaths); var dep = AddCommonPackageIds(new Dependency( - group, artifact, version, packageIds: packageIds, + group, artifact, version, classifier: classifier, + packageIds: packageIds, repositories: UniqueList(depRepoList).ToArray(), createdBy: createdBy)); clientDependenciesMap[dep.Key] = dep; diff --git a/source/JarResolverTests/JarResolverTests.csproj b/source/JarResolverTests/JarResolverTests.csproj deleted file mode 100644 index 4a9acd0d..00000000 --- a/source/JarResolverTests/JarResolverTests.csproj +++ /dev/null @@ -1,75 +0,0 @@ - - - - Debug - AnyCPU - {593254D7-6358-40A6-B0C8-F0616BBF499D} - Library - JarResolverTests - JarResolverTests - v2.0 - 1.2 - 12.0.0 - 2.0 - - - True - full - False - bin\Debug - DEBUG; - prompt - 4 - False - - - none - True - bin\Release - prompt - 4 - False - - - ..\packages\NUnit.2.6.3\lib\ - - - - - - $(NUnityHintPath)/nunit.framework.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - {CC4F239D-3C7F-4164-830F-9215AE15B32A} - JarResolverLib - - - - - - diff --git a/source/JarResolverTests/packages.config b/source/JarResolverTests/packages.config deleted file mode 100644 index d4e241a2..00000000 --- a/source/JarResolverTests/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/source/PackageManager/src/Constants.cs b/source/PackageManager/src/Constants.cs deleted file mode 100644 index ad07bfe2..00000000 --- a/source/PackageManager/src/Constants.cs +++ /dev/null @@ -1,96 +0,0 @@ -// -// Copyright (C) 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Google.PackageManager { - /// - /// Collection of constants relating to the PackageManager. - /// - public class Constants { - /// - /// The name of the plugin package manifest file. - /// - public const string MANIFEST_FILE_NAME = "package-manifest.xml"; - /// - /// The name of the plugin description file. - /// - public const string DESCRIPTION_FILE_NAME = "description.xml"; - /// - /// A marker used to prefix asset strings. - /// - public const string GPM_LABEL_MARKER = "gpm"; - /// - /// Key value used to get/set Unity editor user preferences for registry - /// location data. - /// - public const string KEY_REGISTRIES = "gpm_registries"; - /// - /// Key value for get/set Unity editor user preferences for location of - /// the package manager download cache location. - /// - public const string KEY_DOWNLOAD_CACHE = "gpm_local_cache"; - /// - /// The constant value of the download cache directory name. - /// - public const string GPM_CACHE_NAME = "GooglePackageManagerCache"; - - // TODO(krispy): when final location is settled it should go here - public const string DEFAULT_REGISTRY_LOCATION = - "https://raw.githubusercontent.com/kuccello/gpm_test/master/registry.xml"; - /// - /// The verbose package mananger logging key. - /// - public const string VERBOSE_PACKAGE_MANANGER_LOGGING_KEY = "gpm_verbose"; - /// - /// The show install assets key. - /// - public const string SHOW_INSTALL_ASSETS_KEY = "gpm_showInstallAssets"; - /// - /// The string key binder used in key string concatenation - /// - public const string STRING_KEY_BINDER = ":"; - /// - /// The gpm label key used in labeling assets - /// - public const string GPM_LABEL_KEY = "key"; - /// - /// The gpm label client used in labeling assets - /// - public const string GPM_LABEL_CLIENT = "client"; - /// - /// The fetch timout threshold - no fetch for external data should take longer - /// - public const double FETCH_TIMOUT_THRESHOLD = 10.0d; - /// - /// The gpm deps xml postfix used to check for package deps. - /// - public const string GPM_DEPS_XML_POSTFIX = "gpm.dep.xml"; - /// - /// The android sdk root Unity editor preference key. - /// - public const string ANDROID_SDK_ROOT_PREF_KEY = "AndroidSdkRoot"; - /// - /// The project settings key. - /// - public const string PROJECT_SETTINGS_KEY = "ProjectSettings"; - /// - /// The project record filename stored above Assets - /// - public const string PROJECT_RECORD_FILENAME = "project.gpm.xml"; - /// - /// The version unknown marker. - /// - public const string VERSION_UNKNOWN = "-"; - } -} diff --git a/source/PackageManager/src/Controllers.cs b/source/PackageManager/src/Controllers.cs deleted file mode 100644 index 8775aefb..00000000 --- a/source/PackageManager/src/Controllers.cs +++ /dev/null @@ -1,1783 +0,0 @@ -// -// Copyright (C) 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Google.PackageManager { - using System; - using System.Collections.Generic; - using System.IO; - using System.Xml.Serialization; - using JarResolver; - using UnityEditor; - using UnityEngine; - - /// - /// Logging controller used for logging messages. All member classes of PackageManager that - /// need to log strings should use this controller as it is environment aware and can be used - /// during testcase execution. - /// - public static class LoggingController { - public static bool testing = false; - /// - /// Log the specified msg to the context appropriate console. - /// - /// Message to write to console. - public static void Log(string msg) { - if (!testing) { - if (SettingsController.VerboseLogging) { - Debug.Log(msg); - } - } else { - Console.WriteLine(msg); - } - } - /// - /// Logs the warning to the context appropriate console. - /// - /// Message to write as a warning to console. - public static void LogWarning(string msg) { - if (!testing) { - if (SettingsController.VerboseLogging) { - Debug.LogWarning(msg); - } - } else { - Console.WriteLine(msg); - } - } - /// - /// Logs the error to the context appropriate console - /// - /// Message to write as an error to the console. - public static void LogError(string msg) { - if (!testing) { - Debug.LogError(msg); - } else { - Console.WriteLine(msg); - } - } - } - - /// - /// Unity editor prefs abstraction. Mirrors partial API of Unity EditorPrefs. - /// - public interface IEditorPrefs { - void DeleteAll(); - void DeleteKey(string key); - bool GetBool(string key, bool defaultValue = false); - float GetFloat(string key, float defaultValue = 0.0F); - int GetInt(string key, int defaultValue = 0); - string GetString(string key, string defaultValue = ""); - bool HasKey(string key); - void SetBool(string key, bool value); - void SetFloat(string key, float value); - void SetInt(string key, int value); - void SetString(string key, string value); - } - - /// - /// Unity environment data abstraction interface to allow decoupling and - /// module isolation. - /// - public interface IUnityEnvironmentData { - string GetApplicationDataPath(); - } - - /// - /// Unity environment data implementation used as default implementation. - /// - public class UnityEnvironmentData : IUnityEnvironmentData { - public string GetApplicationDataPath() { - return Application.dataPath; - } - } - - /// - /// Unity editor prefs implementation. The reason this exists is to allow for - /// the separation of UnityEditor calls from the controllers. Used to support - /// testing and enforce cleaner separations. Mirrors partial API of Unity - /// EditorPrefs class. - /// - public class UnityEditorPrefs : IEditorPrefs { - public void DeleteAll() { - EditorPrefs.DeleteAll(); - } - - public void DeleteKey(string key) { - EditorPrefs.DeleteKey(key); - } - - public bool GetBool(string key, bool defaultValue = false) { - return EditorPrefs.GetBool(key, defaultValue); - } - - public float GetFloat(string key, float defaultValue = 0) { - return EditorPrefs.GetFloat(key, defaultValue); - } - - public int GetInt(string key, int defaultValue = 0) { - return EditorPrefs.GetInt(key, defaultValue); - } - - public string GetString(string key, string defaultValue = "") { - return EditorPrefs.GetString(key, defaultValue); - } - - public bool HasKey(string key) { - return EditorPrefs.HasKey(key); - } - - public void SetBool(string key, bool value) { - EditorPrefs.SetBool(key, value); - } - - public void SetFloat(string key, float value) { - EditorPrefs.SetFloat(key, value); - } - - public void SetInt(string key, int value) { - EditorPrefs.SetInt(key, value); - } - - public void SetString(string key, string value) { - EditorPrefs.SetString(key, value); - } - } - - /// - /// UnityController acts as a wrapper around Unity APIs that other controllers use. - /// This is useful during testing as it allows separation of concerns. - /// - public static class UnityController { - public static IEditorPrefs EditorPrefs { get; private set; } - public static IUnityEnvironmentData EnvironmentData { get; private set; } - static UnityController() { - EditorPrefs = new UnityEditorPrefs(); - EnvironmentData = new UnityEnvironmentData(); - } - - /// - /// Swaps the environment data. - /// - /// New env data. - public static void SwapEnvironmentData(IUnityEnvironmentData newEnvData) { - EnvironmentData = newEnvData; - } - - /// - /// Swaps the editor prefs. Exposed for testing. - /// - /// New editor prefs. - public static void SwapEditorPrefs(IEditorPrefs newEditorPrefs) { - EditorPrefs = newEditorPrefs; - } - } - - /// - /// Helper class for interfacing with package manager specific settings. - /// - public static class SettingsController { - /// - /// Location on filesystem where downloaded packages are stored. - /// - public static string DownloadCachePath { - get { - return UnityController.EditorPrefs.GetString(Constants.KEY_DOWNLOAD_CACHE, - GetDefaultDownloadPath()); - } - set { - if (Directory.Exists(value)) { - UnityController.EditorPrefs.SetString(Constants.KEY_DOWNLOAD_CACHE, value); - } else { - throw new Exception("Download Cache location does not exist: " + - value); - } - } - } - /// - /// Verbose logging property flag. - /// - public static bool VerboseLogging { - get { - return UnityController.EditorPrefs.GetBool( - Constants.VERBOSE_PACKAGE_MANANGER_LOGGING_KEY, true); - } - set { - UnityController.EditorPrefs.SetBool( - Constants.VERBOSE_PACKAGE_MANANGER_LOGGING_KEY, value); - } - } - /// - /// Determines if the user should be able to see the plugin package files - /// before installing a plugin. - /// - public static bool ShowInstallFiles { - get { - return UnityController.EditorPrefs.GetBool(Constants.SHOW_INSTALL_ASSETS_KEY, - true); - } - set { - UnityController.EditorPrefs.SetBool(Constants.SHOW_INSTALL_ASSETS_KEY, value); - } - } - - /// - /// Gets the default download cache path. This is where plugin packages - /// are downloaded before installation. - /// - /// The default download path. - static string GetDefaultDownloadPath() { - return Path.Combine(Environment.GetFolderPath( - Environment.SpecialFolder.LocalApplicationData), - Constants.GPM_CACHE_NAME); - } - } - - /// - /// Response codes used by controllers in PackageManager. - /// - public enum ResponseCode { - /// - /// Registry already in memory - /// - REGISTRY_ALREADY_PRESENT, - /// - /// Registry was added to recorded preferences - /// - REGISTRY_ADDED, - /// - /// Registry was removed from recorded preferences - /// - REGISTRY_REMOVED, - /// - /// Registry that was requested could not be found - /// - REGISTRY_NOT_FOUND, - /// - /// Requested plugin install halted because it is already installed - /// - PLUGIN_ALREADY_INSTALLED, - /// - /// Could not process the plugin binary package - /// - PLUGIN_BINARY_ERROR, - /// - /// Plugin was successfully installed - /// - PLUGIN_INSTALLED, - /// - /// Plugin resolution (getting its data from source) succeeded - /// - PLUGIN_RESOLVED, - /// - /// Plugin was successfully removed - /// - PLUGIN_REMOVED, - /// - /// Plugin metadata was not processed due to an error - /// - PLUGIN_METADATA_FAILURE, - /// - /// Requested plugin was not found based on information provided - /// - PLUGIN_NOT_FOUND, - /// - /// Plugin is not installed in the project - /// - PLUGIN_NOT_INSTALLED, - /// - /// Plugin removal halted because of an error - /// - PLUGIN_NOT_REMOVED, - /// - /// Fetch of data resulted in an error - /// - FETCH_ERROR, - /// - /// Fetch of data took too long and exceeded timout period - /// - FETCH_TIMEOUT, - /// - /// Fetch of data completed successfully - /// - FETCH_COMPLETE, - /// - /// The XML data caused an exception - /// - XML_INVALID, - /// - /// The URI value does not point to a reachable location or is not well formed - /// - URI_INVALID, - } - - /// - /// URI data fetcher interface. - /// - public interface IUriDataFetcher { - /// - /// Thread blocking fetch for a string result. - /// - /// The fetch as string. - /// URI. - /// Result string. - ResponseCode BlockingFetchAsString(Uri uri, out string result); - /// - /// Thread blocking fetch for a byte[] result. - /// - /// The fetch as bytes. - /// URI. - /// Result bytes. - ResponseCode BlockingFetchAsBytes(Uri uri, out byte[] result); - } - - /// - /// URI fetcher. b/34930031 investigate using external fetch through compiled python lib - /// - public class UriFetcher : IUriDataFetcher { - byte[] bytesResult; - string textResult; - bool isForBytes = false; - - private ResponseCode DoBlockingFetch(Uri uri) { - var www = new WWW(uri.AbsoluteUri); - double startTime = EditorApplication.timeSinceStartup; - while (www.error == null && !www.isDone) { - var elapsed = EditorApplication.timeSinceStartup - startTime; - if (elapsed > Constants.FETCH_TIMOUT_THRESHOLD) { - LoggingController.Log("Fetch threshold exceeded."); - return ResponseCode.FETCH_TIMEOUT; - } - } - if (www.error != null) { - LoggingController.Log(www.error); - return ResponseCode.FETCH_ERROR; - } - if (isForBytes) { - bytesResult = www.bytes; - } else { - textResult = www.text; - } - return ResponseCode.FETCH_COMPLETE; - } - - /// - /// Blocking fetch of URI where the expected result is returned as - /// byte information. - /// - /// A byte array. - /// URI location to fetch from. - /// Result is the container that holds the result - /// byte data. - public ResponseCode BlockingFetchAsBytes(Uri uri, out byte[] result) { - isForBytes = true; - ResponseCode rc = DoBlockingFetch(uri); - result = bytesResult; - return rc; - } - - /// - /// Blocking fetch of URI where the expected result is returned as a - /// string. - /// - /// A string. - /// URI location to fetch from. - /// Result is the container that holds the result - /// string data. - public ResponseCode BlockingFetchAsString(Uri uri, out string result) { - ResponseCode rc = DoBlockingFetch(uri); - result = textResult; - return rc; - } - } - - /// - /// Intermediator URI data fetch controller. - /// - public static class UriDataFetchController { - public static IUriDataFetcher uriFetcher = new UriFetcher(); - /// - /// Swaps the URI data fetcher with another instance. Used in testcases - /// to setup deterministic execution. - /// - /// New fetcher. - public static void SwapUriDataFetcher(IUriDataFetcher newFetcher) { - uriFetcher = newFetcher; - } - } - - /// - /// Some constants are inconvenient for testing purposes. This class wraps the constants that - /// may be changed during test execution. - /// - public static class TestableConstants { - public static bool testcase = false; - static string debugDefaultRegistryLocation = Constants.DEFAULT_REGISTRY_LOCATION; - - /// - /// Gets or sets the default registry location. Is environment context aware. - /// - /// The default registry location. - public static string DefaultRegistryLocation { - get { - return debugDefaultRegistryLocation; - } - /// - /// Sets the default registry location only if testing enabled. - /// - /// Value. - set { - if (testcase) { - debugDefaultRegistryLocation = value; - } else { - LoggingController.LogError( - "Attempted to set DefaultRegistryLocation outside of testcase."); - } - } - } - } - - /// - /// Registry wrapper pairs Uri of registry with Registry object. This allows - /// for a cleaner separation of model and logic. This makes it easier to key - /// a specific Registry model object on its registered Uri. - /// - public class RegistryWrapper { - /// - /// Gets or sets the location of the Registry held in Model - /// - /// The location. - public Uri Location { get; set; } - /// - /// Gets or sets the model which is the actual registry. - /// - /// The model. - public Registry Model { get; set; } - } - - /// - /// Registry manager controller responsible for adding/removing known registry locations. The - /// set of known registries is available across all Unity projects. - /// - public static class RegistryManagerController { - /// - /// Registry database used to hold known registries, serializeable to xml. - /// - [XmlRoot("regdb")] - public class RegistryDatabase : PackageManagerModel { - [XmlArray("registries")] - [XmlArrayItem("reg-uri-string")] - public HashSet registryLocation = new HashSet(); - [XmlElement("lastUpdate")] - public string lastUpdate; - [XmlIgnore] - public Dictionary wrapperCache = - new Dictionary(); - } - - /// - /// The registry database of all recorded registry locations. Serialzed/Deserialized to - /// editor prefs. - /// - static RegistryDatabase regDb; - - /// - /// Initializes the class. - /// - static RegistryManagerController() { - LoadRegistryDatabase(); - } - - /// - /// Saves the registry database to editor prefs. - /// - static void SaveRegistryDatabase() { - if (regDb == null) { - return; - } - regDb.lastUpdate = DateTime.UtcNow.ToString("o"); - var xml = regDb.SerializeToXMLString(); - UnityController.EditorPrefs.SetString(Constants.KEY_REGISTRIES, xml); - } - - /// - /// Inflates the registry database from Unity editor preferences key. - /// Force reloading causes a new object instance to be created. - /// - /// If set to true force reload. - public static void LoadRegistryDatabase(bool forceReload = false) { - var existingRegDB = regDb; - var regDbXml = UnityController.EditorPrefs.GetString(Constants.KEY_REGISTRIES, null); - if ((regDbXml == null || regDbXml.Length == 0) && existingRegDB == null) { - CreateRegistryDatabase(); - } else { - var readRegDb = RegistryDatabase.LoadFromString(regDbXml); - - if (existingRegDB != null) { - // compare time stamps - var existingComparedToOther = Convert.ToDateTime(existingRegDB.lastUpdate) - .CompareTo(Convert.ToDateTime(readRegDb.lastUpdate)); - if (existingComparedToOther > 0 || forceReload) { - // The existing db is newer than the loaded one or we are forcing a refresh - // The source of truth is always what has been recorded to the editor pref. - regDb = readRegDb; - } - } else { - regDb = readRegDb; - } - } - if (regDb.registryLocation.Count == 0) { - // really there is no good reason to have an empty registry database - AddRegistry(new Uri(TestableConstants.DefaultRegistryLocation)); - } - RefreshRegistryCache(); - } - - /// - /// Creates a new registry database instance with a default registry. - /// Will indirectly destroy existing registry database held in memory. - /// - static void CreateRegistryDatabase() { - regDb = new RegistryDatabase(); - regDb.registryLocation.Add(TestableConstants.DefaultRegistryLocation); - regDb.lastUpdate = DateTime.UtcNow.ToString("o"); - } - - /// - /// Refreshes the registry cache for the provided RegistryWrapper. - /// - /// Wrapper. - public static void RefreshRegistryCache(RegistryWrapper wrapper = null) { - var regLocs = new List(regDb.registryLocation); - if (wrapper != null) { - regLocs.Clear(); - regLocs.Add(wrapper.Location.AbsoluteUri); - } - - foreach (var regUri in regLocs) { - var uri = new Uri(regUri); - string xmlData; - ResponseCode rc = UriDataFetchController - .uriFetcher.BlockingFetchAsString(uri, out xmlData); - if (rc != ResponseCode.FETCH_COMPLETE) { - LoggingController.LogError( - string.Format("Failed attempt to fetch {0} got response code {1}", regUri, rc)); - continue; - } - try { - regDb.wrapperCache[uri] = new RegistryWrapper { - Location = uri, - Model = Registry.LoadFromString(xmlData) - }; - } catch (Exception e) { - LoggingController.LogError( - string.Format("EXCEPTION: {0} inflating Registry {1} using returned xml." + - "\n\n{2}\n\n", e, regUri, xmlData)); - continue; - } - } - } - - /// - /// Attempts to add a registry to the known set of registries using the - /// provided Uri. If the Uri turns out to be a registry location that is - /// not already part of the known set of registries then it is added to - /// the set of known registries. Once a valid registry Uri has been provided - /// it will persist across Unity projects. - /// - /// A - /// URI. - public static ResponseCode AddRegistry(Uri uri) { - if (regDb != null && regDb.registryLocation.Contains(uri.AbsoluteUri)) { - return ResponseCode.REGISTRY_ALREADY_PRESENT; - } - - string xmlData; - ResponseCode rc = UriDataFetchController - .uriFetcher.BlockingFetchAsString(uri, out xmlData); - if (rc != ResponseCode.FETCH_COMPLETE) { - LoggingController.LogError( - string.Format("Attempted fetch of {0} got response code {1}", - uri.AbsoluteUri, rc)); - return rc; - } - - try { - var reg = Registry.LoadFromString(xmlData); - regDb.registryLocation.Add(uri.AbsoluteUri); - SaveRegistryDatabase(); - regDb.wrapperCache[uri] = new RegistryWrapper { Location = uri, Model = reg }; - var xml = regDb.SerializeToXMLString(); - UnityController.EditorPrefs.SetString(Constants.KEY_REGISTRIES, xml); - } catch (Exception e) { - LoggingController.LogError( - string.Format("EXCEPTION Adding Registry {0}: \n\n{1}", uri.AbsoluteUri, e)); - return ResponseCode.XML_INVALID; - } - - return ResponseCode.REGISTRY_ADDED; - } - - /// - /// Attempts to remove the registry identified by the uri. - /// - /// A ResponseCode - /// URI. - public static ResponseCode RemoveRegistry(Uri uri) { - if (uri == null) { - LoggingController.LogWarning("Attempted to remove a registry with null uri."); - return ResponseCode.URI_INVALID; - } - if (regDb == null || regDb.wrapperCache == null) { - LoadRegistryDatabase(); - } - - if (!regDb.wrapperCache.ContainsKey(uri)) { - LoggingController.LogWarning( - string.Format("No registry to remove at {0}. Not found in cache.", - uri.AbsoluteUri)); - return ResponseCode.REGISTRY_NOT_FOUND; - } - regDb.wrapperCache.Remove(uri); - regDb.registryLocation.Remove(uri.AbsoluteUri); - SaveRegistryDatabase(); - var xml = regDb.SerializeToXMLString(); - UnityController.EditorPrefs.SetString(Constants.KEY_REGISTRIES, xml); - return ResponseCode.REGISTRY_REMOVED; - } - - /// - /// Gets all wrapped registries. - /// - /// All wrapped registries. - public static List AllWrappedRegistries { - get { - var result = new List(); - result.AddRange(regDb.wrapperCache.Values); - return result; - } - } - - /// - /// Gets the URI for registry. - /// - /// The URI for registry or null if not found. - /// Registry object - public static Uri GetUriForRegistry(Registry reg) { - if (reg == null) { - return null; - } - Uri result = null; - foreach (var wrapper in regDb.wrapperCache.Values) { - if (wrapper.Model.GenerateUniqueKey().Equals(reg.GenerateUniqueKey())) { - result = wrapper.Location; - break; - } - } - return result; - } - } - - /// - /// Packaged plugin wrapper class that binds multiple models togeather. This - /// makes it easier to pass around a bundled representation of a packaged - /// plugin. - /// - public class PackagedPlugin { - /// - /// Gets or sets the parent registry which is the owner of this plugin. - /// - /// The parent registry. - public Registry ParentRegistry { get; set; } - /// - /// Gets or sets the meta data which holds the details about the plugin. - /// - /// The meta data. - public PluginMetaData MetaData { get; set; } - /// - /// Gets or sets the description for the release version of the plugin. - /// - /// The description. - public PluginDescription Description { get; set; } - /// - /// Gets or sets the location of where the MetaData came from. - /// - /// The location. - public Uri Location { get; set; } - } - - /// - /// Plugin manager controller. - /// - public static class PluginManagerController { - /// - /// The plugin cache associates RegistryWrapper keys to lists of PackagedPlugins that are - /// part of the set of plugins defined by the Registry. - /// - static Dictionary> pluginCache = - new Dictionary>(); - /// - /// The plugin map is used to quickly lookup a specific plugin using its unique key. - /// - static Dictionary pluginMap = - new Dictionary(); - /// - /// Versionless plugin map is used to quickly lookup a specific plugin using its group and - /// artifact. - /// - static Dictionary versionlessPluginMap = - new Dictionary(); - - /// - /// Creates the versionless key. - /// - /// The versionless key. - /// A PackagedPlugin - public static string CreateVersionlessKey(PackagedPlugin plugin) { - return string.Join(Constants.STRING_KEY_BINDER, new string[] { - plugin.MetaData.groupId, - plugin.MetaData.artifactId - }); - } - - /// - /// Changes a versioned plugin unique key into a versionless one. - /// - /// Versionless plugin key. - /// Versioned plugin key. - public static string VersionedPluginKeyToVersionless(string versionedPluginKey) { - return versionedPluginKey - .Substring(0, versionedPluginKey.LastIndexOf(Constants.STRING_KEY_BINDER)); - } - - /// - /// Uses the versionless key to lookup and return a packaged plugin. If - /// no matching packaged plugin exists then returns null. The format of - /// the versionless key is "plugin-groupId:plugin-artifactId". - /// - /// The plugin for versionless key. - /// Versionless plugin key. - public static PackagedPlugin GetPluginForVersionlessKey(string versionlessKey) { - PackagedPlugin plugin = null; - versionlessPluginMap.TryGetValue(versionlessKey, out plugin); - return plugin; - } - - /// - /// Gets the list of all plugins across all registered registries. If - /// refresh is true then the plugin data returned is guaranteed to be - /// up to date since each plugin source data is fetched prior to this - /// method returning. - /// - /// The list of all plugins. - /// If set to true refresh. - public static List GetListOfAllPlugins(bool refresh = false) { - var result = new List(); - foreach (var wr in RegistryManagerController.AllWrappedRegistries) { - result.AddRange(GetPluginsForRegistry(wr, refresh)); - } - return result; - } - - /// - /// Gets the plugins for provided registry. May try to resolve the data for the registry - /// plugin modules if registry has not been seen before or if the cache was flushed. - /// - /// The plugins for registry or null if there was a failure. - /// RegistryWrapper - public static List GetPluginsForRegistry(RegistryWrapper regWrapper, - bool refresh = false) { - var pluginList = new List(); - if (regWrapper == null) { - return pluginList; - } - if (!refresh) { // Just return what ever is known currently. - if (pluginCache.TryGetValue(regWrapper, out pluginList)) { - // data available - return pluginList; - } - // did not find anything for the registry so we need to return an empty list - return new List(); - } - - PurgeFromCache(regWrapper); - - pluginList = new List(); - // now there is no trace of plugins from the registry - time to rebuild data from source - // the module locations are known once a registry is resolved - foreach (var module in regWrapper.Model.modules.module) { - // Is the module a remote or local? - Uri pluginModuleUri = ResolvePluginModuleUri(regWrapper, module); - PackagedPlugin plugin; - ResponseCode rc = - ResolvePluginDetails(pluginModuleUri, regWrapper.Model, out plugin); - switch (rc) { - case ResponseCode.PLUGIN_RESOLVED: - // resolved so we add it - pluginList.Add(plugin); - AddOrUpdatePluginMap(plugin); - AddOrUpdateVersionlessPluginMap(plugin); - break; - default: - LoggingController.LogWarning( - string.Format("Plugin not resolved: {0} for uri {1}", rc, pluginModuleUri)); - break; - } - } - - pluginCache[regWrapper] = pluginList; - - foreach (var plugin in pluginList) { - var versionLessKey = CreateVersionlessKey(plugin); - pluginMap[versionLessKey] = plugin; - } - return pluginList; - } - - /// - /// Purges all plugins belonging to the provided registry wrapper from controller cache. - /// - /// Registry wrapper that is the parent of all the plugins to - /// purge from the controller cache. - static void PurgeFromCache(RegistryWrapper regWrapper) { - List pluginList; - // a refresh has been requested - fetch is implied - // this is a refresh so we remove the stale data first - pluginCache[regWrapper] = null; - RegistryManagerController.RefreshRegistryCache(regWrapper); - // purge the correct plugins from the cache - if (pluginCache.TryGetValue(regWrapper, out pluginList)) { - if (pluginList == null) { - pluginCache[regWrapper] = new List(); - } else { - foreach (var plugin in pluginList) { - pluginMap[CreateVersionlessKey(plugin)] = null; - } - } - } - } - - /// - /// Adds the plugin to or updates the versionless plugin map plugin reference. - /// - /// Plugin. - static void AddOrUpdateVersionlessPluginMap(PackagedPlugin plugin) { - versionlessPluginMap[CreateVersionlessKey(plugin)] = plugin; - } - - /// - /// Adds the plugin to or updates the plugin map plugin reference. - /// - /// Plugin. - static void AddOrUpdatePluginMap(PackagedPlugin plugin) { - pluginMap[plugin.MetaData.UniqueKey] = plugin; - } - - /// - /// Resolves the plugin module URI since it could be local relative to the registry or a - /// fully qualified Uri. - /// - /// The plugin module URI. - /// Reg wrapper. - /// Module location. - static Uri ResolvePluginModuleUri(RegistryWrapper regWrapper, string moduleLoc) { - Uri pluginModuleUri; - try { - pluginModuleUri = new Uri(moduleLoc); - } catch { - // if local then rewrite as remote relative - pluginModuleUri = ChangeRegistryUriIntoModuleUri(regWrapper.Location, moduleLoc); - } - - return pluginModuleUri; - } - - /// - /// Resolves the plugin details. Is public in so that individual plugins can be re-resolved - /// which is needed in some situations. - /// - /// The plugin details. - /// URI. - /// Parent. - /// Plugin. - public static ResponseCode ResolvePluginDetails(Uri uri, Registry parent, - out PackagedPlugin plugin) { - string xmlData; - ResponseCode rc = UriDataFetchController - .uriFetcher.BlockingFetchAsString(uri, out xmlData); - if (rc != ResponseCode.FETCH_COMPLETE) { - plugin = null; - return rc; - } - PluginMetaData metaData; - PluginDescription description; - try { - metaData = PluginMetaData.LoadFromString(xmlData); - Uri descUri = GenerateDescriptionUri(uri, metaData); - rc = UriDataFetchController.uriFetcher.BlockingFetchAsString(descUri, out xmlData); - if (rc != ResponseCode.FETCH_COMPLETE) { - plugin = null; - return rc; - } - description = PluginDescription.LoadFromString(xmlData); - } catch (Exception e) { - Console.WriteLine(e); - plugin = null; - return ResponseCode.PLUGIN_METADATA_FAILURE; - } - plugin = new PackagedPlugin { - Description = description, - Location = uri, - MetaData = metaData, - ParentRegistry = parent - }; - return ResponseCode.PLUGIN_RESOLVED; - } - - /// - /// Generates the description URI. - /// Public for testing. - /// - /// The description URI. - /// Plugin URI. - /// Meta data. - public static Uri GenerateDescriptionUri(Uri pluginUri, PluginMetaData metaData) { - var descUri = new Uri(Utility.GetURLMinusSegment(pluginUri.AbsoluteUri)); - descUri = new Uri(descUri, metaData.artifactId + "/"); - descUri = new Uri(descUri, metaData.versioning.release + "/"); - descUri = new Uri(descUri, Constants.DESCRIPTION_FILE_NAME); - return descUri; - } - - /// - /// Generates the binary URI location based on the plugin meta data and source of the - /// metadata. - /// Public for testing. - /// - /// The binary package URI. - /// Plugin metadata URI. - /// Meta data object for plugin. - public static Uri GenerateBinaryUri(Uri pluginUri, PluginMetaData metaData) { - var descUri = new Uri(Utility.GetURLMinusSegment(pluginUri.AbsoluteUri)); - descUri = new Uri(descUri, metaData.artifactId + "/"); - descUri = new Uri(descUri, metaData.versioning.release + "/"); - descUri = new Uri(descUri, GenerateBinaryFilename(metaData)); - return descUri; - } - - /// - /// Changes the registry URI into module URI. - /// Public for testing. - /// - /// A registry Uri related module URI. - /// Registry URI. - /// Local module name. - public static Uri ChangeRegistryUriIntoModuleUri(Uri registryUri, string localModuleName) { - var pluginUri = new Uri(Utility.GetURLMinusSegment(registryUri.AbsoluteUri)); - pluginUri = new Uri(pluginUri, localModuleName + "/"); - pluginUri = new Uri(pluginUri, Constants.MANIFEST_FILE_NAME); - return pluginUri; - } - - /// - /// Refresh the specified registry plugin data. - /// - /// RegistryWrapper with a valid registry. - public static void Refresh(RegistryWrapper regWrapper) { - GetPluginsForRegistry(regWrapper, true); - } - - /// - /// Generates a package binary filename for the plugin. - /// - /// The binary filename. - /// Plugin. - public static string GenerateBinaryFilename(PluginMetaData plugin) { - return string.Format("{0}.{1}", plugin.artifactId, plugin.packaging); - } - } - - /// - /// Unity asset database proxy interface. - /// - public interface IUnityAssetDatabaseProxy { - string[] GetAllAssetPaths(); - string[] GetLabelsForAssetAtPath(string path); - string[] FindAssets(string filter); - string GUIDToAssetPath(string guid); - void SetLabels(string path, string[] labels); - void ImportPackage(string packagePath, bool interactive); - bool DeleteAsset(string path); - string[] FindAssets(string filter, string[] searchInFolders); - void Refresh(ImportAssetOptions options = ImportAssetOptions.Default); - } - - /// - /// Unity engine asset database proxy to intermediate Unity AssertDatabase. - /// - public class UnityEngineAssetDatabaseProxy : IUnityAssetDatabaseProxy { - public bool DeleteAsset(string path) { - return AssetDatabase.DeleteAsset(path); - } - - public string[] FindAssets(string filter) { - return AssetDatabase.FindAssets(filter); - } - - public string[] FindAssets(string filter, string[] searchInFolders) { - return AssetDatabase.FindAssets(filter, searchInFolders); - } - - public string[] GetAllAssetPaths() { - return AssetDatabase.GetAllAssetPaths(); - } - - public string[] GetLabelsForAssetAtPath(string path) { - return AssetDatabase.GetLabels(AssetDatabase.LoadMainAssetAtPath(path)); - } - - public string GUIDToAssetPath(string guid) { - return AssetDatabase.GUIDToAssetPath(guid); - } - - public void ImportPackage(string packagePath, bool interactive) { - AssetDatabase.ImportPackage(packagePath, interactive); - } - - public void SetLabels(string path, string[] labels) { - AssetDatabase.SetLabels(AssetDatabase.LoadMainAssetAtPath(path), labels); - } - - public void Refresh(ImportAssetOptions options = ImportAssetOptions.Default) { - AssetDatabase.Refresh(); - } - } - - /// - /// Asset database controller that acts as an intermediary. Test cases can swap out the backing - /// databaseProxy instance. - /// - public static class AssetDatabaseController { - public static bool ImportInitiatedFromController { get; private set; } - static IUnityAssetDatabaseProxy databaseProxy = new UnityEngineAssetDatabaseProxy(); - public static void SwapDatabaseProxy(IUnityAssetDatabaseProxy newProxy) { - databaseProxy = newProxy; - } - public static string[] GetAllAssetPaths() { - return databaseProxy.GetAllAssetPaths(); - } - public static string[] GetLabelsForAssetAtPath(string path) { - return databaseProxy.GetLabelsForAssetAtPath(path); - } - public static string[] FindAssets(string filter) { - return databaseProxy.FindAssets(filter); - } - public static string GUIDToAssetPath(string guid) { - return databaseProxy.GUIDToAssetPath(guid); - } - public static void SetLabels(string path, string[] labels) { - databaseProxy.SetLabels(path, labels); - } - public static void ImportPackage(string packagePath, bool interactive) { - ImportInitiatedFromController = true; - databaseProxy.ImportPackage(packagePath, interactive); - } - public static bool DeleteAsset(string path) { - return databaseProxy.DeleteAsset(path); - } - public static string[] FindAssets(string filter, string[] searchInFolders) { - return databaseProxy.FindAssets(filter, searchInFolders); - } - public static void Refresh() { - databaseProxy.Refresh(); - } - public static void ClearImportFlag() { - ImportInitiatedFromController = false; - } - } - - /// - /// Project manager controller handles actions related to a project like installing, removing - /// plugins. - /// - [InitializeOnLoad] - public static class ProjectManagerController { - static readonly HashSet allProjectAssetLabels = new HashSet(); - static ProjectPackages gpmPackagesInProject; - /// - /// The project dirty flag - set if new plugin package installed or target platform changed - /// - static bool projectDirty = false; - static BuildTarget currentBuildTarget; - - static ProjectManagerController() { - gpmPackagesInProject = InflateProjectRecord(GetProjectRecordPath()); - EditorApplication.update += Update; - currentBuildTarget = EditorUserBuildSettings.activeBuildTarget; - EnsurePluginsDirectory(); - } - - /// - /// Unity editor will call this method during the Unity editor update loop. - /// - static void Update() { - if (EditorUserBuildSettings.activeBuildTarget != currentBuildTarget) { - currentBuildTarget = EditorUserBuildSettings.activeBuildTarget; - EnsurePluginsDirectory(); - projectDirty = true; - } - - if (projectDirty) { - // check project for re-resolve on target if dirty - RefreshProject(); - } - } - - /// - /// Ensures the plugins directory exists, creates if missing. - /// - static void EnsurePluginsDirectory() { - if (!AssetDatabase.IsValidFolder("Assets/Plugins")) { - AssetDatabase.CreateFolder("Assets", "Plugins"); - } - if (!AssetDatabase.IsValidFolder("Assets/Plugins/Android")) { - AssetDatabase.CreateFolder("Assets/Plugins", "Android"); - } - if (!AssetDatabase.IsValidFolder("Assets/Plugins/IOS")) { - AssetDatabase.CreateFolder("Assets/Plugins", "IOS"); - } - - AssetDatabase.Refresh(); - } - - /// - /// Removes the client from the project - /// - /// Client name. - static void RemoveClient(string clientName) { - LoggingController.Log( - string.Format("Removing Client for Key {0}", clientName)); - List clients = GetAllClients(); - if (clients == null) { - LoggingController.LogError( - string.Format("No Client for Key {0}", clientName)); - return; - } - ProjectClient removeMe = null; - foreach (var client in clients) { - if (client.GenerateUniqueKey().StartsWith(clientName)) { - removeMe = client; - LoggingController.Log( - string.Format("Found client object to remove {0}", removeMe)); - } - } - if (removeMe != null) { - gpmPackagesInProject.clients.Remove(removeMe); - WriteProjectPackages(); - LoggingController.Log( - string.Format("Removed client object for {0}", clientName)); - } - } - - /// - /// Refreshes the client, resolves it's dependencies if needed. - /// - /// Client. - static void RefreshClient(ProjectClient client) { - switch (currentBuildTarget) { - case BuildTarget.Android: - if (!client.resolvedForAndroid) { // Android deps have not been resolved - EnsureAllAssetsLabeled(client.Name); - PlayServicesSupport support = null; - if (!PlayServicesSupport.instances.TryGetValue(client.Name, out support)) { - support = PlayServicesSupport.CreateInstance(client.Name, - UnityController.EditorPrefs.GetString( - Constants.ANDROID_SDK_ROOT_PREF_KEY), - Constants.PROJECT_SETTINGS_KEY); - } - foreach (var packageDep in client.clientDependencies.androidDependencies) { - string[] packageIdsArray = null; - if (packageDep.args != null && packageDep.args.packageIds != null) { - packageIdsArray = new string[packageDep.args.packageIds.Count]; - packageDep.args.packageIds.CopyTo(packageIdsArray); - } - string[] repositories = null; - if (packageDep.args != null && packageDep.args.repositories != null) { - repositories = new string[packageDep.args.repositories.Count]; - packageDep.args.repositories.CopyTo(repositories); - } - support.DependOn(packageDep.group, - packageDep.artifact, - packageDep.version, - packageIdsArray, - repositories); - } - - WriteProjectPackages(); - EnsurePluginsDirectory(); - - try { - LoggingController.Log( - string.Format("About to resolve for client: {0}", client.Name)); - GooglePlayServices.PlayServicesResolver.Resolver.DoResolution(support, - "Assets/Plugins/Android", - () => { - AssetDatabase.Refresh(); - LoggingController.Log( - string.Format("Android resolution complete for client: {0}", - client.Name)); - client.resolvedForAndroid = true; - WriteProjectPackages(); - // tag/label all dependency files additive tags if needed - EnsureLabeledDependencies(client.Name); - }); - } catch (Exception e) { - LoggingController.LogError( - string.Format("EXCEPTION during Android resolve dependencies: {0}\n{1}", - e, e.StackTrace)); - } - } - break; - case BuildTarget.iOS: - // TODO: b/34936552 implement for iOS POD deps - break; - default: - break; - } - } - - /// - /// Ensures all assets belonging to a packaged plugin are labeled. - /// - /// Client name. - static void EnsureAllAssetsLabeled(string clientName) { - var client = GetClientForKey(clientName); - if (client == null) { - return; - } - foreach (var assetPath in client.assets) { - var asset = AssetDatabase.LoadMainAssetAtPath(assetPath); - if (asset != null) { - var existingLabels = AssetDatabase.GetLabels(asset); - var labelSet = new HashSet(); - labelSet.Add(Constants.GPM_LABEL_MARKER); - labelSet.Add(string.Join(Constants.STRING_KEY_BINDER, new string[] { - Constants.GPM_LABEL_MARKER, - Constants.GPM_LABEL_CLIENT, - clientName - })); - labelSet.Add(string.Join(Constants.STRING_KEY_BINDER, new string[] { - Constants.GPM_LABEL_MARKER, - Constants.GPM_LABEL_KEY, - clientName, - client.version - })); - labelSet.UnionWith(existingLabels); - var labels = new string[labelSet.Count]; - labelSet.CopyTo(labels); - AssetDatabase.SetLabels( - asset, - labels); - } - } - } - - /// - /// Ensures the dependency assets are labeled for the client. - /// - /// Client name. - static void EnsureLabeledDependencies(string clientName) { - var client = GetClientForKey(clientName); - if (client == null) { - return; - } - foreach (var depName in client.depNames) { - switch (currentBuildTarget) { - case BuildTarget.Android: - var name = depName.Substring(depName.IndexOf(':') + 1); - var assets = AssetDatabase.FindAssets(name, new string[] { - "Assets/Plugins/Android"}); - if (assets.Length > 0) { - var asset = AssetDatabase.LoadMainAssetAtPath( - AssetDatabase.GUIDToAssetPath(assets[0])); - if (asset != null) { - var existingLabels = AssetDatabase.GetLabels(asset); - var labelSet = new HashSet(); - labelSet.Add(Constants.GPM_LABEL_MARKER); - labelSet.Add(string.Join(Constants.STRING_KEY_BINDER, new string[] { - Constants.GPM_LABEL_MARKER, - Constants.GPM_LABEL_CLIENT, - clientName - })); - labelSet.UnionWith(existingLabels); - var labels = new string[labelSet.Count]; - labelSet.CopyTo(labels); - AssetDatabase.SetLabels( - asset, - labels); - } - } - break; - case BuildTarget.iOS: - LoggingController.LogError("IOS EnsureLabeledDependencies Not Implemented."); - break; - } - } - } - - /// - /// Refreshes the project by reading project file, checking each client package to see if - /// the current target platform has been resolved and if not then it resolves and updates - /// the list of assets associated with the client. - /// - static void RefreshProject() { - var clients = GetAllClients(); - foreach (var client in clients) { - RefreshClient(client); - } - } - - /// - /// Reloads the project packages from the xml file living above the Assets directory. - /// - static void ReloadProjectPackages() { - gpmPackagesInProject = InflateProjectRecord(GetProjectRecordPath()); - } - - /// - /// Writes the project packages to an xml file living above the Assets directory. - /// - static void WriteProjectPackages() { - if (gpmPackagesInProject != null) { - try { - File.WriteAllText(GetProjectRecordPath(), - gpmPackagesInProject.SerializeToXMLString()); - } catch (Exception e) { - LoggingController.LogError( - string.Format("Could not write project file due to exception - {0}", e)); - } - } - } - - /// - /// Gets the client for client name or creates new one. - /// - /// The client for client name. - /// Client name (versionless plugin key). - public static ProjectClient GetClientForKey(string clientName) { - LoggingController.Log( - string.Format("Getting Client for Key {0}", clientName)); - List clients = GetAllClients(); - if (clients == null) { - return null; - } - ProjectClient projectClient = null; - foreach (var client in clients) { - try { - var v = client.GenerateUniqueKey(); - if (PluginManagerController - .VersionedPluginKeyToVersionless(v).Equals(clientName)) { - // client exists in project - LoggingController.Log( - string.Format("Discovered client {0} in project record.", clientName)); - projectClient = client; - } - } catch { - // an un-initialized client in set - skip - continue; - } - } - if (projectClient == null) { - LoggingController.Log( - string.Format("Initialized new client {0}. Call SaveClient to persist.", - clientName)); - projectClient = new ProjectClient(); - string[] keyComponents = clientName.Split(Constants.STRING_KEY_BINDER[0]); - projectClient.groupId = keyComponents[0]; - projectClient.artifactId = keyComponents[1]; - projectClient.version = Constants.VERSION_UNKNOWN; - gpmPackagesInProject.clients.Add(projectClient); - } - return projectClient; - } - - /// - /// Gets all clients listed in the project.xml that lives above the Assets directory. - /// - /// The all clients. - public static List GetAllClients() { - if (gpmPackagesInProject == null) { - ReloadProjectPackages(); - if (gpmPackagesInProject == null) { - // no project to load - return null; - } - } - return gpmPackagesInProject.clients; - } - - /// - /// Removes the client record for key from the project.xml that lives above the Assets - /// directory as well as the in memory model object. - /// - /// Client name. - public static void RemoveClientForKey(string clientName) { - var client = GetClientForKey(clientName); - if (client != null) { - gpmPackagesInProject.clients.Remove(client); - WriteProjectPackages(); - } - } - - /// - /// Saves the client the project.xml that lives above the Assets directory as well as the - /// in memory model object. - /// - /// Client name. - public static void SaveClient(string clientName) { - var client = GetClientForKey(clientName); - if (client != null) { - WriteProjectPackages(); - } - } - - /// - /// Inflates the project record from an xml file at the path provided. - /// - /// The project record. - /// Project file. - static ProjectPackages InflateProjectRecord(string projectFile) { - ProjectPackages result = null; - if (File.Exists(projectFile)) { - try { - result = ProjectPackages.LoadFromFile(projectFile); - } catch (Exception e) { - LoggingController.LogError(string.Format("Exception loading project meta: {0}", - e)); - } - } - if (result == null) { - result = new ProjectPackages(); - LoggingController.Log( - string.Format("Project GPM data does not exist. Creating new object.")); - } - return result; - } - - /// - /// Gets the project record path that lives above the Assets directory. - /// - /// The project record path. - static string GetProjectRecordPath() { - return Path.Combine(Path.Combine( - UnityController.EnvironmentData.GetApplicationDataPath(), ".."), - Constants.PROJECT_RECORD_FILENAME); - } - - /// - /// Refreshes the list of asset labels that are present in the current project. - /// This method does NOT modify the asset labels on assets, it just reads them all. - /// - public static void RefreshListOfAssetLabels() { - allProjectAssetLabels.Clear(); - var paths = AssetDatabaseController.GetAllAssetPaths(); - foreach (var path in paths) { - var labels = AssetDatabaseController.GetLabelsForAssetAtPath(path); - if (labels.Length > 0) { - foreach (var label in labels) { - allProjectAssetLabels.Add(label); - } - } - } - } - - /// - /// Checks if the plugin identified by pluginKey is installed in project. - /// - /// true, if plugin installed in project was ised, false - /// otherwise. - /// Plugin key. - public static bool IsPluginInstalledInProject(string pluginKey) { - var versionlessKey = PluginManagerController.VersionedPluginKeyToVersionless(pluginKey); - var listOfClients = GetAllClients(); - if (listOfClients == null) { - return false; - } - foreach (var client in listOfClients) { - // Versionless comparison here - if (PluginManagerController - .VersionedPluginKeyToVersionless(client.GenerateUniqueKey()) - .Equals(versionlessKey)) { - return true; - } - } - return false; - } - - /// - /// Checks if the specific plugin version is installed. - /// - /// true, if plugin version in project, false otherwise. - /// Plugin key (includes version). - public static bool IsPluginVersionInProject(string pluginKey) { - var listOfClients = GetAllClients(); - if (listOfClients == null) { - return false; - } - foreach (var client in listOfClients) { - if (client.GenerateUniqueKey().Equals(pluginKey)) { - return true; - } - } - return false; - } - - /// - /// Installs the plugin into the current project. Will download the plugin package if needed - /// but will check for local copy first in the download cache location. Calls the resolve - /// dependencies regardless of if auto-resolution is enabled. - /// - /// A response code - /// Plugin key. - public static ResponseCode InstallPlugin(string pluginKey) { - LoggingController.Log( - string.Format("Attempt install of plugin with key {0}.", pluginKey)); - // Is the plugin already installed and is it available to install. - if (IsPluginInstalledInProject(pluginKey)) { - LoggingController.Log("Plugin already installed!"); - return ResponseCode.PLUGIN_ALREADY_INSTALLED; - } - - // non-version check - bool upgrade = IsPluginInstalledInProject(pluginKey); - - var versionlessKey = PluginManagerController.VersionedPluginKeyToVersionless(pluginKey); - PackagedPlugin plugin = - PluginManagerController.GetPluginForVersionlessKey(versionlessKey); - if (plugin == null) { - LoggingController.Log( - string.Format("Versionless Plugin key {0} was not found.", versionlessKey)); - return ResponseCode.PLUGIN_NOT_FOUND; - } - // Generate download uri to binary and location it will be stored. - var subPath = - plugin.MetaData.UniqueKey.Replace(Constants.STRING_KEY_BINDER, - string.Format("{0}", - Path.DirectorySeparatorChar)); - var fileSystemLocation = new Uri(SettingsController.DownloadCachePath).AbsolutePath; - var packageDirectory = Path.Combine(fileSystemLocation, subPath); - var binaryFileName = PluginManagerController.GenerateBinaryFilename(plugin.MetaData); - var fullPathFileName = Path.Combine(packageDirectory, binaryFileName); - var binaryUri = PluginManagerController.GenerateBinaryUri( - plugin.Location, plugin.MetaData); - LoggingController.Log( - string.Format("Checking {0} for existing binary package...", fullPathFileName)); - if (!File.Exists(fullPathFileName)) { - Directory.CreateDirectory(packageDirectory); - // Download the binary data into the download cache location if needed. - LoggingController.Log("Binary package not found. Will attempt to download..."); - byte[] data; - ResponseCode rc = - UriDataFetchController - .uriFetcher.BlockingFetchAsBytes(binaryUri, out data); - if (ResponseCode.FETCH_COMPLETE != rc) { - // Something went wrong - abort. - LoggingController.Log( - string.Format("Download of plugin binary data failed. {0}, {1}", - rc, - binaryUri)); - return ResponseCode.PLUGIN_BINARY_ERROR; - } - LoggingController.Log( - string.Format("Binary package downloaded from {0}. " + - "Writting to file system {1}...", - binaryUri.AbsoluteUri, - fullPathFileName)); - File.WriteAllBytes(fullPathFileName, data); - } - LoggingController.Log(string.Format("File {0} exists, importing now.", - fullPathFileName)); - // Perform the import of the binary from the download location. - AssetDatabaseController.ImportPackage(fullPathFileName, - SettingsController.ShowInstallFiles); - // If auto deps resolution not on then call resolve deps. - if (!VersionHandler.Enabled) { - VersionHandler.UpdateVersionedAssets(true); - } - - return ResponseCode.PLUGIN_INSTALLED; - } - - /// - /// Uninstalls the plugin from the current project. - /// - /// The plugin. - /// Plugin key. - public static ResponseCode UninstallPlugin(string pluginKey) { - LoggingController.Log( - string.Format("Remove Plugin for key: {0}", pluginKey)); - - var clientName = - PluginManagerController - .VersionedPluginKeyToVersionless(pluginKey); - - // Get the client from the project. - var client = GetClientForKey(clientName); - if (client == null) { - return ResponseCode.PLUGIN_NOT_FOUND; - } - - var assetPathsToDelete = new List(); - var clientSpecificLabels = new HashSet(); - clientSpecificLabels.Add(string.Join(Constants.STRING_KEY_BINDER, new string[] { - Constants.GPM_LABEL_MARKER, - Constants.GPM_LABEL_CLIENT, - clientName - })); - clientSpecificLabels.Add(string.Join(Constants.STRING_KEY_BINDER, new string[] { - Constants.GPM_LABEL_MARKER, - Constants.GPM_LABEL_KEY, - clientName, - client.version - })); - // Check for co-ownership through labels of all assets and dependencies. - var allAssetPathsToCheck = new List(); - // All the non-plugins path assets (the ones that were actually imported). - allAssetPathsToCheck.AddRange(client.assets); - - // This gets all the deps paths in Android plugins. - foreach (var depName in client.depNames) { - var name = depName.Substring(depName.IndexOf(':') + 1); - var assets = AssetDatabase.FindAssets(name, new string[] { - "Assets/Plugins/Android"}); - if (assets.Length > 0) { - allAssetPathsToCheck.Add(AssetDatabase.GUIDToAssetPath(assets[0])); - } - } - - foreach (var assetPath in allAssetPathsToCheck) { - var asset = AssetDatabase.LoadMainAssetAtPath(assetPath); - if (asset != null) { - - var existingAssetLabelSet = new HashSet(); - existingAssetLabelSet.UnionWith(AssetDatabase.GetLabels(asset)); - - var remaining = new HashSet(existingAssetLabelSet); - remaining.ExceptWith(clientSpecificLabels); - - if (remaining.Count == 1 && remaining.Contains(Constants.GPM_LABEL_MARKER)) { - // no co-ownership - ok to delete - assetPathsToDelete.Add(assetPath); - } else if (remaining.Count > 1 && - remaining.Contains(Constants.GPM_LABEL_MARKER)) { - // co-owned - remove client labels - var labels = new string[remaining.Count]; - remaining.CopyTo(labels); - AssetDatabase.SetLabels( - asset, - labels); - } - } - } - - // Suspend resolution. - bool vhEnabled = VersionHandler.Enabled; - VersionHandler.Enabled = false; - - try { - PlayServicesSupport.instances[clientName] = null; - - foreach (var assetPathToDelete in assetPathsToDelete) { - AssetDatabase.DeleteAsset(assetPathToDelete); - } - RemoveClient(clientName); - - AssetDatabaseController.Refresh(); - } catch (Exception ex) { - LoggingController.LogError(ex.ToString()); - } finally { - // Restore resume resolution if was suspended. - VersionHandler.Enabled = vhEnabled; - } - - return ResponseCode.PLUGIN_REMOVED; - } - - /// - /// Package postprocessor. - /// - public class PackagePostprocessor : AssetPostprocessor { - /// - /// This is called after importing of any number of assets is complete (when the Assets - /// progress bar has reached the end). - /// - static void OnPostprocessAllAssets(string[] importedAssets, - string[] deletedAssets, - string[] movedAssets, - string[] movedFromAssetPaths) { - - bool wasTriggeredByPackageManager = - AssetDatabaseController.ImportInitiatedFromController; - - var depsModels = new List(); - // look for *gpm.dep.xml in importedAssets - // TODO: b/34936751 handle case of multiple gpm.dep.xml files found - foreach (string str in importedAssets) { - if (str.EndsWith(Constants.GPM_DEPS_XML_POSTFIX)) { - // this is the deps file - resolve fully - var res = File.ReadAllText(Path.GetFullPath(str)); - try { - depsModels.Add(PackageDependencies.LoadFromString(res)); - } catch (Exception e) { - LoggingController.Log(string.Format("{0}: \n{1}", e, res)); - } - LoggingController.Log( - string.Format("OnPostprocessAllAssets: Dependencies xml: {0}", res)); - } - } - foreach (var depsModel in depsModels) { - ProcessDepModel(depsModel, importedAssets); - } - - foreach (var deleted in deletedAssets) { - LoggingController.Log(string.Format("Observed deletion of: {0}", deleted)); - } - } - } - - /// - /// Processes the dependency model with the assets being imported. - /// - /// Deps model. - /// Imported assets. - static void ProcessDepModel(PackageDependencies depsModel, string[] importedAssets) { - string clientName = string.Format("{0}:{1}", depsModel.groupId, depsModel.artifactId); - LoggingController.Log("Dependencies Client: " + clientName); - var projectClient = GetClientForKey(clientName); - if (projectClient.version.Equals(Constants.VERSION_UNKNOWN)) { - // new packaged plugin install - projectClient.version = depsModel.version; - } else { - // new version being installed over an older version? - // TODO: b/34936656 version compare - make sure new version is newer or same - // - if newer then need to remove all old assets - } - - projectClient.assets.AddRange(importedAssets); - projectClient.clientDependencies = depsModel; - SaveClient(clientName); - // mark project dirty - projectDirty = true; - } - } -} diff --git a/source/PackageManager/src/Models.cs b/source/PackageManager/src/Models.cs deleted file mode 100644 index cfc6b60a..00000000 --- a/source/PackageManager/src/Models.cs +++ /dev/null @@ -1,427 +0,0 @@ -// -// Copyright (C) 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Google.PackageManager { - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Xml.Serialization; - - /// - /// Abstract base class for XML serializable model classes. Derived model - /// classes get implementations of common methods for load and save of model - /// data. - /// - public abstract class PackageManagerModel { - /// - /// The xml model version. Used to detect and handle future model - /// changes. - /// - [XmlElement("xmlModelVersion")] - public string xmlModelVersion; - - // TODO: b/34936401 add xmlModelVersion validation. - - /// - /// Deserializes a model from a provided stream containing XML data for the model. - /// - /// The from stream. - /// Reader. - public static T LoadFromStream(StreamReader reader) { - return (T)((new XmlSerializer(typeof(T)).Deserialize(reader))); - } - - /// - /// Deserializes a model from a specified XML model file. - /// - /// The inflated model object. Will throw an exception if the - /// file was not found. - /// The XML file path to read from. - public static T LoadFromFile(string file) { - return LoadFromStream(new StreamReader(file, Encoding.UTF8, true)); - } - - /// - /// Builds model tree from string containing valid XML. - /// - /// The model built from the provided utf-8 string. - /// Xml data encoded in utf-8. - public static T LoadFromString(string xmlData) { - return LoadFromStream( - new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(xmlData)))); - } - - /// - /// Serializes the model to an XML string. - /// - /// An XML formatted string representing the model state. - /// - public string SerializeToXMLString() { - var serializer = new XmlSerializer(typeof(T)); - var memoryStream = new MemoryStream(); - var streamWriter = new StreamWriter(memoryStream, Encoding.UTF8); - serializer.Serialize(streamWriter, this); - byte[] utf8EncodedXml = memoryStream.ToArray(); - return Encoding.UTF8.GetString(utf8EncodedXml, 0, utf8EncodedXml.Length); - } - } - - /// - /// Abstract base class for XML serializable model classes. Derived model - /// classes get implementations of common methods for asset labels and keys. - /// - public abstract class PackageManagerLabeledModel : PackageManagerModel { - /// - /// The group identifier based on Maven POM model. - /// https://maven.apache.org/guides/mini/guide-naming-conventions.html - /// - [XmlElement("groupId")] - public string groupId; - - /// - /// The artifact identifier based on Maven POM model. - /// https://maven.apache.org/guides/mini/guide-naming-conventions.html - /// - [XmlElement("artifactId")] - public string artifactId; - - /// - /// The version based on Maven POM model. - /// https://maven.apache.org/guides/mini/guide-naming-conventions.html - /// - [XmlElement("version")] - public string version; - - /// - /// Meta information for the plugin encoded as a string. - /// If groupId, artifactId or version are not set then this value will be null. - /// - /// The asset label. - public string GenerateAssetLabel() { - if (groupId == null || artifactId == null || version == null) { - return null; - } - return string.Join( - Constants.STRING_KEY_BINDER, - new string[]{ - Constants.GPM_LABEL_MARKER, - Constants.GPM_LABEL_KEY, - GenerateUniqueKey() - }); - } - - /// - /// Generates the unique key based on the current groupId, artifactId and version. - /// - /// The unique key. - public string GenerateUniqueKey() { - if (groupId == null || artifactId == null || version == null) { - throw new Exception(string.Format("Attempted to generate unique " + - "key on object {0} without setting " + - "object state. [groupId = {1}, artifactId = " + - "{2}, version = {3}]", this, ""+groupId, - ""+artifactId, ""+version)); - } - return string.Join(Constants.STRING_KEY_BINDER, - new string[] { groupId, artifactId, version }); - } - } - - /// - /// Registry model class contains details about registered plugins which are - /// part of a specific registry instance. - /// - /// A registry is a URI addressable XML model that references a set of - /// PackageManager compatable packaged plugins. A registry encapsulates the - /// concept of a lookup group. - /// - [XmlRoot("registry")] - public class Registry : PackageManagerLabeledModel { - /// - /// When was this registry last updated in epoch time. - /// - [XmlElement("lastUpdated")] - public long lastUpdated; - /// - /// The modules (packaged plugin references) contained within this registry. - /// - [XmlElement("modules")] - public Modules modules = new Modules(); - } - - /// - /// Modules is a container that holds the list of registered plugins. It is - /// used to keep the XML model clean and readable. - /// - public class Modules : PackageManagerModel { - /// - /// A module as represented in this model can be one of the following: - /// 1) A groupId of a packaged plugin, if that packaged plugin is relative to the registry. - /// (meaning that it has a path that is a decendent of the registry Uri path) - /// 2) An absolute Uri to a location of a packaged plugin's package-manifest.xml - /// - /// Note: It is permitted to mix representations in a registry model. Meaning you can have - /// a mix of groupId representations and absolute Uri representations in the same registry. - /// - [XmlElement("module")] - public List module = new List(); - } - - /// - /// Plugin meta data associated with a plugin for use in package management. - /// This model contains the bulk of the packaged plugin details used for - /// managing plugin installation and deletion. - /// - [XmlRoot("metadata")] - public class PluginMetaData : PackageManagerLabeledModel { - /// - /// Format eg. 1.2.3 - /// - [XmlElement("modelVersion")] - public string modelVersion; - - /// - /// What format the binary package is in. Will usually be "unitypackage". - /// - [XmlElement("packaging")] - public string packaging; - - /// - /// A container element that holds a list of all versions the plugin has. - /// - [XmlElement("versioning")] - public Versioning versioning = new Versioning(); - - /// - /// The ISO 8601 date this plugin was last updated. - /// - [XmlElement("lastUpdated")] - public long lastUpdated; - - /// - /// UniqueKey format: groupId:artifactId:version - /// - /// The unique key. - [XmlIgnore] - public string UniqueKey { - get { - return GenerateUniqueKey(); - } - } - } - - /// - /// Versioning is a container class for release information and other available - /// versions of the plugin. - /// - public class Versioning : PackageManagerModel { - /// - /// The currently released version of the plugin package. This value is - /// used to help select the best version to install in a project. - /// - [XmlElement("release")] - public string release; - - /// - /// The available and published versions of the packaged plugin. - /// - [XmlArray("versions")] - [XmlArrayItem("version")] - public HashSet versions = new HashSet(); - } - - /// - /// A description of the packaged plugin in terms of a specific language code. - /// - /// TODO(krispy): add support for additional languages - /// - [XmlRoot("language")] - public class Language : PackageManagerModel { - /// - /// The lang code is used to indicate the language used in the description. - /// Currently only a value of 'en' is supported by the UI. - /// - [XmlAttribute("type")] - public string langCode; - - /// - /// The name of the plugin in a human readable form. - /// - [XmlElement("name")] - public string name; - - /// - /// A short description of the plugin. Usually a single marketing sentence. - /// - [XmlElement("short")] - public string shortDesc; - - /// - /// A long form description of the packaged plugin. Used to describe features - /// and bug fixes. - /// - [XmlElement("full")] - public string fullDesc; - } - - /// - /// Plugin description containing the text details about a plugin as will be - /// presented to the user in the Package Manager UI. - /// - [XmlRoot("description")] - public class PluginDescription : PackageManagerModel { - /// - /// The set of descriptions for this packaged plugin. - /// - [XmlArray("languages")] - [XmlArrayItem("language")] - public List languages = new List(); - } - - /// - /// Package dependencies model representing the JarResolver dependencies a package plugin has. - /// - [XmlRoot("package-dependencies")] - public class PackageDependencies : PackageManagerLabeledModel { - /// - /// The root dependencies that the Unity plugin package has. - /// - [XmlArray("android-dependencies")] - [XmlArrayItem("android-dependency")] - public List androidDependencies = - new List(); - /// - /// The iOS Pod dependencies. - /// - [XmlArray("ios-pod-dependencies")] - [XmlArrayItem("ios-pod-dependency")] - public List iOSDependencies = new List(); - } - - /// - /// IOS Package dependency model representing a specific iOS dependency. - /// - public class IOSPodDependency : PackageManagerModel { - /// - /// The name of the POD. - /// - [XmlElement("name")] - public string name; - /// - /// The version of the POD. - /// - [XmlElement("version")] - public string version; - } - - /// - /// Package dependency model representing a specific android dependency. - /// - public class AndroidPackageDependency : PackageManagerModel { - /// - /// The group identifier for the Android dependency. eg "com.google.android.gms" - /// - [XmlElement("group")] - public string group; - /// - /// The artifact identifier for the Android dependency. eg. "play-services-ads" - /// - [XmlElement("artifact")] - public string artifact; - /// - /// The flexible version identifier for the Android dependency. eg. "LATEST", "23.1+" etc. - /// - [XmlElement("version")] - public string version; - /// - /// The arguments to support where to find details about the dependency. - /// - [XmlElement("args")] - public DependencyArgument args = new DependencyArgument(); - } - - /// - /// Dependency argument set containing sets of android packages and repositories - /// - public class DependencyArgument : PackageManagerModel { - /// - /// Maps to the concept of PlayServicesSupport/Dependency packageIds. - /// Eg. {"extra-google-m2repository","extra-android-m2repository"} - /// - [XmlArray("android-packages")] - [XmlArrayItem("android-package")] - public List packageIds = new List(); - /// - /// Maps to the concept of PlayServicesSupport/Dependency repositories - /// - [XmlArray("repositories")] - [XmlArrayItem("repository")] - public List repositories = new List(); - } - - /// - /// Project packages model used to represent a manifest of what packaged plugins have been added - /// to the project. This model supports the ability to cleanly remove packaged plugins without - /// relying on the JarResolver. - /// - [XmlRoot("gpm-project")] - public class ProjectPackages : PackageManagerModel { - [XmlArray("clients")] - [XmlArrayItem("client")] - public List clients = new List(); - } - - /// - /// Project client represents an installed packaged plugin with all known - /// data associated with its installation into a project. - /// - public class ProjectClient : PackageManagerLabeledModel { - /// - /// The client dependencies declared in the gpm.dep.xml for the package - /// - [XmlElement("package-dependencies")] - public PackageDependencies clientDependencies; - /// - /// The assets - all known that belong to this package - /// - [XmlArray("assets")] - [XmlArrayItem("asset")] - public List assets = new List(); - /// - /// List of versionless asset names from resolution - /// - [XmlArray("resolved-dep-names")] - [XmlArrayItem("dep-name")] - public List depNames = new List(); - /// - /// Has this package resolved its android deps? - /// - [XmlElement("android-resolved")] - public bool resolvedForAndroid = false; - /// - /// Has this package resolved its ios deps? - /// - [XmlElement("ios-resolved")] - public bool resolvedForIOS = false; - - [XmlIgnore] - public string Name { - get { - return string.Format("{0}{1}{2}",groupId, Constants.STRING_KEY_BINDER, artifactId); - } - } - } -} \ No newline at end of file diff --git a/source/PackageManager/src/Utilities.cs b/source/PackageManager/src/Utilities.cs deleted file mode 100644 index 1085d7dc..00000000 --- a/source/PackageManager/src/Utilities.cs +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (C) 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Google.PackageManager { - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - - /// - /// A collection of useful utility methods. - /// - public static class Utility { - public static void EnsureDirectory(string directoryPath) { - (new DirectoryInfo(directoryPath)).Create(); - } - - /// - /// Checks that the URI is valid in terms of what Unity supports. - /// - /// true, if URI string is valid, false - /// otherwise. - /// URI. - public static bool IsValidUriString(string uri) { - Uri uriResult; - return Uri.TryCreate(uri,UriKind.Absolute, out uriResult); - } - - /// - /// Returns an absolute Uri string with one segment removed from the end. - /// - /// For example: - /// http://domain.com/segment1/segment2/segment3 - /// - /// would be returned as - /// - /// http://domain.com/segment1/segment2/ - /// - /// Also: - /// - /// http://domain.com/segment1/segment2/segment3 - /// and - /// http://domain.com/segment1/segment2/segment3/ - /// - /// would be treated the same regardless of the trailing slash. - /// - /// The absolute Uri minus the last segment. - /// URI to remove segment from - public static string GetURLMinusSegment(string uri) { - Uri outUri; - Uri.TryCreate(uri,UriKind.Absolute, out outUri); - return outUri.AbsoluteUri.Remove(outUri.AbsoluteUri.Length - - outUri.Segments.Last().Length); - } - } -} \ No newline at end of file diff --git a/source/PackageManager/src/Views.cs b/source/PackageManager/src/Views.cs deleted file mode 100644 index 8a4652dc..00000000 --- a/source/PackageManager/src/Views.cs +++ /dev/null @@ -1,427 +0,0 @@ -// -// Copyright (C) 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Google.PackageManager { - using System; - using System.Collections.Generic; - using UnityEditor; - using UnityEngine; - - /// - /// Registry manager view provides the UI within Unity's editor allowing the user - /// to manage the set of registered registries. - /// - public class RegistryManagerView : EditorWindow { - Vector2 scrollPos; - string newRegUri; - bool registryDataDirty = true; - Stack installRegistry = new Stack(); - Stack uninstallRegistry = new Stack(); - - void OnInspectorGUI() { - EditorUtility.SetDirty(this); - } - - /// - /// Update override method called by Unity editor. The update cycle looks - /// for any action events that were generated in the OnGUI method by the - /// user and takes action on those events. - /// - void Update() { - if (registryDataDirty) { - LoggingController.Log("plugin data marked dirty - refreshing..."); - registryDataDirty = false; - RegistryManagerController.RefreshRegistryCache(); - EditorUtility.SetDirty(this); - } - - while (installRegistry.Count > 0) { - var regUriStr = installRegistry.Pop(); - try { - ResponseCode rc = RegistryManagerController.AddRegistry(new Uri(regUriStr)); - if (ResponseCode.REGISTRY_ADDED == rc) { - registryDataDirty = true; - } else if (ResponseCode.REGISTRY_ALREADY_PRESENT == rc) { - EditorUtility.DisplayDialog("Registry Already Present", - "The registry was NOT added since it" + - "is already known.", - "Ok"); - } else { - EditorUtility.DisplayDialog("Registry Location Not Valid", - string.Format( - "The registry cannot be added. An " + - "error has occurred using the provided " + - "location.\n\n{0}", rc), - "Ok"); - } - } catch (Exception e) { - // failure - bad data - EditorUtility.DisplayDialog("Registry Location Processing Error", - string.Format("A processing exception was " + - "generated while trying to add {0}." + - "\n\n{1}", regUriStr, e), - "Ok"); - } - } - - while (uninstallRegistry.Count > 0) { - var regUriStr = uninstallRegistry.Pop(); - if (EditorUtility.DisplayDialog("Confirm Delete Registry", - "Are you sure you want to delete the registry?", - "Yes Delete It!", - "Cancel")) { - ResponseCode rc = RegistryManagerController - .RemoveRegistry(new Uri(regUriStr)); - registryDataDirty = true; - if (ResponseCode.REGISTRY_NOT_FOUND == rc) { - EditorUtility.DisplayDialog("Registry Not Found!", - "There was a problem while trying to " + - "remove the registry. It was not " + - "found when we tried to remove it." - , "Ok"); - } - } - } - } - - void OnGUI() { - using (var h = new EditorGUILayout.HorizontalScope()) { - using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPos)) { - scrollPos = scrollView.scrollPosition; - RenderAddRegistryForm(); - RenderListOfRegistries(); - } - } - } - - /// - /// Renders the list of known registries and interactive UI component to remove a - /// registry entry. - /// - void RenderListOfRegistries() { - try { - foreach (var wrappedReg in RegistryManagerController.AllWrappedRegistries) { - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField(wrappedReg.Model.GenerateUniqueKey()); - GUI.backgroundColor = Color.red; - if (GUILayout.Button("Remove Registry")) { - uninstallRegistry.Push(wrappedReg.Location.AbsoluteUri); - } - EditorGUILayout.EndHorizontal(); - EditorGUILayout.LabelField(wrappedReg.Location.AbsoluteUri); - } - } catch (Exception e) { - Debug.LogError(e); - } - } - - /// - /// Renders UI with interactive UI components allowing the user to add a - /// registry location Uri. - /// - void RenderAddRegistryForm() { - EditorGUILayout.BeginHorizontal(); - newRegUri = EditorGUILayout.TextField("New Registry Uri:", newRegUri); - GUI.backgroundColor = Color.green; - if (GUILayout.Button("Add Registry")) { - if (newRegUri != null && newRegUri.Length > 12) { // min chars 12 - installRegistry.Push(newRegUri); - newRegUri = ""; - } - } - EditorGUILayout.EndHorizontal(); - } - - /// - /// Adds the Registries menu item in Unity Editor. - /// - [MenuItem("Assets/Play Services Resolver/Package Manager/Registries")] - public static void ShowRegistriesManagerWindow() { - var window = (RegistryManagerView)EditorWindow.GetWindow( - typeof(RegistryManagerView), true, "Package Manager Registries"); - window.Show(); - } - } - - /// - /// Shows the available plugins and allows user to install/remove plugins - /// from project using UI. - /// - public class PluginManagerView : EditorWindow { - Vector2 scrollPos; - List plugins; - bool pluginDataDirty = true; - Stack installPlugins = new Stack(); - HashSet installingPlugins = new HashSet(); - Stack uninstallPlugins = new Stack(); - HashSet uninstallingPlugins = new HashSet(); - Stack moreInfoPlugins = new Stack(); - - /// - /// Called by Unity editor when the Window is created and becomes active. - /// - void OnEnable() { - plugins = PluginManagerController.GetListOfAllPlugins(true); - } - - void OnInspectorGUI() { - EditorUtility.SetDirty(this); - } - - /// - /// Ensures that the plugin details are up to date for rendering UI elements. - /// - void RefreshPluginDataForWindow() { - plugins = PluginManagerController.GetListOfAllPlugins(true); - } - - /// - /// Update override method called by Unity editor. The update cycle looks - /// for any action events that were generated in the OnGUI method by the - /// user and takes action on those events. - /// - void Update() { - if (pluginDataDirty) { - pluginDataDirty = false; - RefreshPluginDataForWindow(); - } - - while (installPlugins.Count > 0) { - var pluginKey = installPlugins.Pop(); - ResponseCode rc = ProjectManagerController.InstallPlugin(pluginKey); - if (ResponseCode.PLUGIN_NOT_INSTALLED == rc) { - EditorUtility.DisplayDialog("Plugin Install Error", - "There was a problem installing the selected plugin.", - "Ok"); - LoggingController.LogError( - string.Format("Could not install plugin with key {0}." + - "Got {1} response code.", pluginKey, rc)); - } else { - pluginDataDirty = true; - } - installingPlugins.Remove(pluginKey); - } - - while (moreInfoPlugins.Count > 0) { - var pluginKey = moreInfoPlugins.Pop(); - var plugin = PluginManagerController.GetPluginForVersionlessKey( - PluginManagerController.VersionedPluginKeyToVersionless(pluginKey)); - // popup with full description - EditorUtility.DisplayDialog( - string.Format("{0}", plugin.MetaData.artifactId), - plugin.Description.languages[0].fullDesc, - "Ok"); - } - - while (uninstallPlugins.Count > 0) { - var pluginKey = uninstallPlugins.Pop(); - ResponseCode rc = ProjectManagerController.UninstallPlugin(pluginKey); - if (ResponseCode.PLUGIN_NOT_REMOVED == rc) { - EditorUtility.DisplayDialog("Plugin Uninstall Error", - "There was a problem removing the selected plugin.", - "Ok"); - LoggingController.LogError( - string.Format("Could not uninstall plugin with key {0}." + - "Got {1} response code.", pluginKey, rc)); - } else { - pluginDataDirty = true; - } - uninstallingPlugins.Remove(pluginKey); - } - } - - void OnGUI() { - using (var h = new EditorGUILayout.HorizontalScope()) { - using (var scrollView = new EditorGUILayout.ScrollViewScope(scrollPos)) { - scrollPos = scrollView.scrollPosition; - foreach (var plugin in plugins) { - RenderPluginDetails(plugin); - } - if (GUILayout.Button("Force Refresh")) { - pluginDataDirty = true; - } - } - } - } - - void RenderPluginDetails(PackagedPlugin plugin) { - GUILayout.Space(5); - GUI.backgroundColor = Color.white; - EditorGUILayout.Separator(); - EditorGUILayout.LabelField("Registry: " + plugin.ParentRegistry.GenerateUniqueKey()); - EditorGUILayout.Separator(); - // Name - Version - Short Desc - var v = string.Format("{0} - version: {1} '{2}'", - plugin.MetaData.artifactId, - plugin.MetaData.version, - plugin.Description - .languages[0].shortDesc); - var pluginKey = string.Format("{0}", plugin.MetaData.UniqueKey); - - EditorGUILayout.LabelField(v); - // [More Info] - [Install|Remove|Update] - EditorGUILayout.BeginHorizontal(); - - GUI.backgroundColor = Color.cyan; - if (GUILayout.Button("More Info")) { - moreInfoPlugins.Push(pluginKey); - } - - if (ProjectManagerController.IsPluginInstalledInProject(plugin.MetaData.UniqueKey)) { - // delete or update - GUI.backgroundColor = Color.red; - if (GUILayout.Button("Uninstall")) { - uninstallPlugins.Push(pluginKey); - uninstallingPlugins.Add(pluginKey); - } - } else if (installingPlugins.Contains(pluginKey)) { - GUI.backgroundColor = Color.gray; - if (GUILayout.Button("Installing...")) { - } - } else if (uninstallingPlugins.Contains(pluginKey)) { - GUI.backgroundColor = Color.blue; - if (GUILayout.Button("Un-Installing...")) { - } - } else { - GUI.backgroundColor = Color.green; - if (GUILayout.Button("Install")) { - installPlugins.Push(pluginKey); - installingPlugins.Add(pluginKey); - } - } - - EditorGUILayout.EndHorizontal(); - GUILayout.Space(5); - } - - /// - /// Adds the Plugins menu item in Unity Editor. - /// - [MenuItem("Assets/Play Services Resolver/Package Manager/Plugins")] - public static void ShowPluginManagerWindow() { - var window = (PluginManagerView)GetWindow( - typeof(PluginManagerView), true, "Package Manager Plugins"); - window.Show(); - } - } - - /// - /// Allows the user to view and make changes to the settings - /// associated with the Package Manager module. - /// - public class SettingsManagerView : EditorWindow { - void OnInspectorGUI() { - EditorUtility.SetDirty(this); - } - - void OnGUI() { - EditorGUILayout.LabelField("Package Manager Settings"); - /// - /// Download Cache Path where the downloaded binary data will be stored. - /// - SettingsController.DownloadCachePath = - EditorGUILayout.TextField("Download Cache Path:", - SettingsController.DownloadCachePath); - /// - /// Display the package contents before installing a plugin. - /// - SettingsController.ShowInstallFiles = - EditorGUILayout.ToggleLeft( - "Show plugin package contents before install?", - SettingsController.ShowInstallFiles); - - /// - /// Toggle Verbose Logging. - /// - SettingsController.VerboseLogging = EditorGUILayout.ToggleLeft( - "Enable Verbose Logging", - SettingsController.VerboseLogging); - - } - - /// - /// Actual menu item for showing Settings. - /// - [MenuItem("Assets/Play Services Resolver/Package Manager/Settings")] - public static void ShowSettingsWindow() { - var window = (SettingsManagerView)EditorWindow.GetWindow( - typeof(SettingsManagerView), true, "Package Manager Settings"); - window.Show(); - } - } - - /// - /// A view that implements logic allowing for context menu - /// option to remove a plugin based on a selected asset. - /// - public static class PluginRemovalContextView { - static readonly List selectionLabels = new List(); - - /// - /// Validates the menu context for RemovePlugin based on selected asset. - /// - /// true, if selected asset validated, false otherwise. - [MenuItem("Assets/Remove Associated Plugin", true)] - static bool ValidateRemovePlugin() { - selectionLabels.Clear(); - // is the current selected asset a GPM labeled asset? - bool gpmAssetLabelFound = false; - var assetGuids = Selection.assetGUIDs; - foreach (var aGuid in assetGuids) { - var asset = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(aGuid)); - var labels = AssetDatabase.GetLabels(asset); - foreach (var label in labels) { - if (label.StartsWith(Constants.GPM_LABEL_MARKER)) { - gpmAssetLabelFound = true; - selectionLabels.Add(label); - } - } - } - return gpmAssetLabelFound; - } - - /// - /// Actual menu item that allows user to remove a plugin based on a selected asset. - /// - [MenuItem("Assets/Remove Associated Plugin")] - public static void RemovePlugin() { - var candidateRemovals = new List(selectionLabels); - var window = (PluginCandidateRemovalWindow)EditorWindow.GetWindow( - typeof(PluginCandidateRemovalWindow), true, "Google Package Manager"); - window.candidateRemovals = candidateRemovals; - window.Show(); - } - } - - /// - /// Plugin candidate removal window. Displays the information required for the - /// user to select what plugins they would like to remove after selecting an asset - /// and choosing to remove the associated plugin. - /// - public class PluginCandidateRemovalWindow : EditorWindow { - void OnInspectorGUI() { - EditorUtility.SetDirty(this); - } - - public List candidateRemovals = new List(); - - void OnGUI() { - // TODO: b/34930539 - foreach (var cr in candidateRemovals) { - EditorGUILayout.ToggleLeft(cr, true); - } - } - } -} \ No newline at end of file diff --git a/source/PackageManagerResolver/PackageManagerResolver.csproj b/source/PackageManagerResolver/PackageManagerResolver.csproj new file mode 100644 index 00000000..dc3963f5 --- /dev/null +++ b/source/PackageManagerResolver/PackageManagerResolver.csproj @@ -0,0 +1,82 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {77EBE819-CBE6-4CA8-A791-ED747EA29D30} + Library + Google + Google.PackageManagerResolver + 1.2 + v3.5 + + + True + full + False + bin\Debug + DEBUG;UNITY_EDITOR + prompt + 4 + False + + + True + full + True + bin\Release + DEBUG;UNITY_EDITOR + prompt + 4 + False + + + ..\..\unity_dlls + + + + $(UnityHintPath)/UnityEditor.dll + + + $(UnityHintPath)/UnityEngine.dll + + + ..\VersionHandler\bin\Release\Google.VersionHandler.dll + + + ..\VersionHandler\bin\Release\Google.VersionHandlerImpl.dll + + + + + + + + + + + + + + + + + + + + + + + + + {5378B37A-887E-49ED-A8AE-42FA843AA9DC} + VersionHandler + + + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + VersionHandlerImpl + + + diff --git a/source/PackageManagerResolver/Properties/AssemblyInfo.cs b/source/PackageManagerResolver/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..39f74645 --- /dev/null +++ b/source/PackageManagerResolver/Properties/AssemblyInfo.cs @@ -0,0 +1,58 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. +[assembly: AssemblyTitle("Google.PackageManagerResolver")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Google LLC")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Copyright 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. +[assembly: AssemblyVersion("1.2.0.0")] + +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. + +// [assembly: AssemblyDelaySign(false)] +// // +// Copyright (C) 2016 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// [assembly: AssemblyKeyFile("")] + +[assembly: InternalsVisibleTo("Google.PackageMigratorIntegrationTests")] +[assembly: InternalsVisibleTo("Google.PackageManagerResolverTests")] +[assembly: InternalsVisibleTo("Google.PackageManagerClientIntegrationTests")] diff --git a/source/PackageManagerResolver/src/PackageManagerClient.cs b/source/PackageManagerResolver/src/PackageManagerClient.cs new file mode 100644 index 00000000..873288ef --- /dev/null +++ b/source/PackageManagerResolver/src/PackageManagerClient.cs @@ -0,0 +1,1372 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using UnityEditor; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; + +namespace Google { + +/// +/// Provides a safe, simple interface that can be used across any Unity version for interaction +/// with the Unity Package Manager. +/// +internal static class PackageManagerClient { + + /// + /// Wrapper object. + /// + public class Wrapper { + + /// + /// Wrapped instance. + /// + protected object instance; + + /// + /// Construct an null wrapper. + /// + public Wrapper() {} + + /// + /// Construct a wrapper around an object. + /// + /// Object to wrap. + public Wrapper(object instance) { + this.instance = instance; + } + + + /// + /// Convert a collection of objects to a field separated string. + /// + /// Collection of objects to convert to a list of strings. + /// The string to use as a separator. + /// A string that consists of members of delimited + /// by string.. + protected static string ObjectCollectionToString(IEnumerable objects, + string separator) { + var components = new List(); + foreach (var obj in objects) components.Add(obj.ToString()); + return String.Join(separator, components.ToArray()); + } + } + + /// + /// Wraps an enumerable list of objects in a collection of wrappers. + /// + private class CollectionWrapper where T : Wrapper, new() { + + /// + /// Objects wrapped in instances of type T. + /// + private ICollection wrapped = new List(); + + /// + /// Wrap an enumerable set of objects by the wrapper class. + /// + public CollectionWrapper(IEnumerable instances) { + if (instances != null) { + foreach (var instance in instances) { + wrapped.Add(Activator.CreateInstance(typeof(T), + new object[] { instance }) as T); + } + } + } + + /// + /// Get the collection. + /// + public ICollection Collection { get { return wrapped; } } + } + + /// + /// Wrapper for PackageManager.Error. + /// + public class Error : Wrapper { + + /// + /// PackageManager.Error type. + /// + private static Type type; + + /// + /// PackageManager.Error.errorCode property. + /// + private static PropertyInfo errorCodeProperty; + + /// + /// PackageManager.Error.message property. + /// + private static PropertyInfo messageProperty; + + /// + /// Message to use if "instance" is null. + /// + private string fallbackMessage; + + /// + /// Wrap the supplied error object. + /// + /// Error instance to wrap. + public Error(object error) : base(error) { + type = type ?? + VersionHandler.FindClass("UnityEditor", "UnityEditor.PackageManager.Error"); + if (type != null) { + errorCodeProperty = errorCodeProperty ?? type.GetProperty("errorCode"); + messageProperty = messageProperty ?? type.GetProperty("message"); + } + instance = error; + fallbackMessage = instance != null ? "Package Manager Not Supported" : ""; + } + + /// + /// Get the error code as a string. + /// + public string ErrorCodeString { + get { + return instance != null ? + errorCodeProperty.GetValue(instance, null).ToString() : ""; + } + } + + /// + /// Get the error message. + /// + public string Message { + get { + return instance != null ? + messageProperty.GetValue(instance, null) as string : fallbackMessage; + } + } + + /// + /// Convert to a string. + /// + /// String representation. + public override string ToString() { + var components = new List(); + var message = Message; + if (!String.IsNullOrEmpty(message)) components.Add(message); + var errorCodeString = ErrorCodeString; + if (!String.IsNullOrEmpty(errorCodeString)) { + components.Add(String.Format("({0})", errorCodeString)); + } + return String.Join(" ", components.ToArray()); + } + } + + /// + /// Wrapper for PackageManager.AuthorInfo. + /// + public class AuthorInfo : Wrapper { + + /// + /// PackageManager.AuthorInfo class. + /// + private static Type type; + + /// + /// PackageManager.AuthorInfo.email property. + /// + private static PropertyInfo emailProperty; + + /// + /// PackageManager.AuthorInfo.name property. + /// + private static PropertyInfo nameProperty; + + /// + /// PackageManager.AuthorInfo.url property. + /// + private static PropertyInfo urlProperty; + + /// + /// Wrap an AuthorInfo instance. + /// + /// Instance to wrap. + public AuthorInfo(object authorInfo) : base(authorInfo) { + type = type ?? + VersionHandler.FindClass("UnityEditor", "UnityEditor.PackageManager.AuthorInfo"); + if (type != null) { + emailProperty = emailProperty ?? type.GetProperty("email"); + nameProperty = nameProperty ?? type.GetProperty("name"); + urlProperty = urlProperty ?? type.GetProperty("url"); + } + instance = authorInfo; + } + + /// + /// email of a package author. + /// + public string Email { get { return emailProperty.GetValue(instance, null) as string; } } + + /// + /// Name of a package author. + /// + public string Name { get { return nameProperty.GetValue(instance, null) as string; } } + + /// + /// URL of a package author. + /// + public string Url { get { return urlProperty.GetValue(instance, null) as string; } } + + /// + /// Convert to a string. + /// + /// String representation. + public override string ToString() { + var components = new string[] {Name, Url, Email}; + var toStringComponents = new List(); + foreach (var component in components) { + if (!String.IsNullOrEmpty(component)) toStringComponents.Add(component); + } + return String.Join(", ", toStringComponents.ToArray()); + } + } + + /// + /// Wrapper for PackageManager.DependencyInfo. + /// + public class DependencyInfo : Wrapper { + + /// + /// PackageManager.DependencyInfo class. + /// + private static Type type; + + /// + /// PackageManager.DependencyInfo.name property. + /// + private static PropertyInfo nameProperty; + + /// + /// PackageManager.DependencyInfo.version property. + /// + private static PropertyInfo versionProperty; + + /// + /// Empty constructor for use in generics. + /// + public DependencyInfo() {} + + /// + /// Wrap a DependencyInfo instance. + /// + /// Instance to wrap. + public DependencyInfo(object dependencyInfo) : base(dependencyInfo) { + type = type ?? + VersionHandler.FindClass("UnityEditor", + "UnityEditor.PackageManager.DependencyInfo"); + if (type != null) { + nameProperty = nameProperty ?? type.GetProperty("name"); + versionProperty = versionProperty ?? type.GetProperty("version"); + } + instance = dependencyInfo; + } + + /// + /// Get the name of the dependency. + /// + public string Name { get { return nameProperty.GetValue(instance, null) as string; } } + + /// + /// Get the version of the dependency. + /// + public string Version { get { return versionProperty.GetValue(instance, null) as string; } } + + /// + /// Convert to a string. + /// + /// String representation. + public override string ToString() { + return String.Format("{0}@{1}", Name, Version); + } + } + + /// + /// Wrapper for PackageManager.VersionsInfo. + /// + public class VersionsInfo : Wrapper { + + /// + /// PackageManager.VersionsInfo type. + /// + private static Type type; + + /// + /// PackageManager.VersionsInfo.all property. + /// + private static PropertyInfo allProperty; + + /// + /// PackageManager.VersionsInfo.compatible property. + /// + private static PropertyInfo compatibleProperty; + + /// + /// PackageManager.VersionsInfo.latest property. + /// + private static PropertyInfo latestProperty; + + /// + /// PackageManager.VersionsInfo.latestCompatible property. + /// + private static PropertyInfo latestCompatibleProperty; + + /// + /// PackageManager.VersionsInfo.recommended property. + /// + private static PropertyInfo recommendedProperty; + + /// + /// Empty constructor for use in generics. + /// + public VersionsInfo() {} + + /// + /// Wrap a VersionsInfo instance. + /// + /// Instance to wrap. + public VersionsInfo(object versionsInfo) : base(versionsInfo) { + type = type ?? + VersionHandler.FindClass("UnityEditor", "UnityEditor.PackageManager.VersionsInfo"); + if (type != null) { + allProperty = allProperty ?? type.GetProperty("all"); + compatibleProperty = compatibleProperty ?? type.GetProperty("compatible"); + latestProperty = latestProperty ?? type.GetProperty("latest"); + latestCompatibleProperty = latestCompatibleProperty ?? + type.GetProperty("latestCompatible"); + recommendedProperty = recommendedProperty ?? type.GetProperty("recommended"); + } + instance = versionsInfo; + } + + /// + /// All versions. + /// + public string[] All { get { return allProperty.GetValue(instance, null) as string[]; } } + + /// + /// Compatible versions for the current version of Unity. + /// + public string[] Compatible { + get { + return compatibleProperty.GetValue(instance, null) as string[]; + } + } + + /// + /// Latest version of the package. + /// + public string Latest { get { return latestProperty.GetValue(instance, null) as string; } } + + /// + /// Latest version compatible with the current version of Unity. + /// + public string LatestCompatible { + get { + return latestCompatibleProperty.GetValue(instance, null) as string; + } + } + + /// + /// Recommended version of the package. + /// + public string Recommended { + get { + return recommendedProperty.GetValue(instance, null) as string; + } + } + } + + /// + /// Wrapper for PackageManager.PackageInfo. + /// + public class PackageInfo : Wrapper { + + /// + /// PackageManager.PackageInfo class. + /// + private static Type typeStore; + + /// + /// Get the PackageInfo class. + /// + public static Type Type { + get { + typeStore = typeStore ?? + VersionHandler.FindClass("UnityEditor", + "UnityEditor.PackageManager.PackageInfo"); + return typeStore; + } + } + + /// + /// PackageManager.PackageInfo.author property. + /// + private static PropertyInfo authorProperty; + + /// + /// PackageManager.PackageInfo.category property. + /// + private static PropertyInfo categoryProperty; + + /// + /// PackageManager.PackageInfo.dependencies property. + /// + private static PropertyInfo dependenciesProperty; + + /// + /// PackageManager.PackageInfo.description property. + /// + private static PropertyInfo descriptionProperty; + + /// + /// PackageManager.PackageInfo.displayName property. + /// + private static PropertyInfo displayNameProperty; + + /// + /// PackageManager.PackageInfo.keywords property. + /// + private static PropertyInfo keywordsProperty; + + /// + /// PackageManager.PackageInfo.name property. + /// + private static PropertyInfo nameProperty; + + /// + /// PackageManager.PackageInfo.packageId property. + /// + private static PropertyInfo packageIdProperty; + + /// + /// PackageManager.PackageInfo.resolvedDependencies property. + /// + private static PropertyInfo resolvedDependenciesProperty; + + /// + /// PackageManager.PackageInfo.version property. + /// + private static PropertyInfo versionProperty; + + /// + /// PackageManager.PackageInfo.versions property. + /// + private static PropertyInfo versionsProperty; + + /// + /// Empty constructor for use in generics. + /// + public PackageInfo() {} + + /// + /// Wrap a packageInfo object. + /// + /// PackageInfo to wrap. + public PackageInfo(object packageInfo) : base(packageInfo) { + var type = Type; + if (type != null) { + authorProperty = authorProperty ?? type.GetProperty("author"); + categoryProperty = categoryProperty ?? type.GetProperty("category"); + dependenciesProperty = dependenciesProperty ?? type.GetProperty("dependencies"); + descriptionProperty = descriptionProperty ?? type.GetProperty("description"); + displayNameProperty = displayNameProperty ?? type.GetProperty("displayName"); + keywordsProperty = keywordsProperty ?? type.GetProperty("keywords"); + nameProperty = nameProperty ?? type.GetProperty("name"); + packageIdProperty = packageIdProperty ?? type.GetProperty("packageId"); + resolvedDependenciesProperty = resolvedDependenciesProperty ?? + type.GetProperty("resolvedDependencies"); + versionProperty = versionProperty ?? type.GetProperty("version"); + versionsProperty = versionsProperty ?? type.GetProperty("versions"); + } + } + + /// + /// Get the package author. + /// + public AuthorInfo Author { + get { + // Author isn't exposed until Unity 2018.3. + var author = authorProperty != null ? authorProperty.GetValue(instance, null) : + null; + return author != null ? new AuthorInfo(author) : null; + } + } + + /// + /// Get the package category. + /// + public string Category { + get { return categoryProperty.GetValue(instance, null) as string; } + } + + /// + /// Get the package dependencies. + /// + public ICollection Dependencies { + get { + // Not available until Unity 2018.3. + return dependenciesProperty != null ? + (new CollectionWrapper( + dependenciesProperty.GetValue(instance, null) as IEnumerable)).Collection : + new DependencyInfo[] {}; + } + } + + /// + /// Get the package description. + /// + public string Description { + get { return descriptionProperty.GetValue(instance, null) as string; } + } + + /// + /// Get the package display name. + /// + public string DisplayName { + get { return displayNameProperty.GetValue(instance, null) as string; } + } + + /// + /// Get the package keywords. + /// + public ICollection Keywords { + get { + // Not available until Unity 2018.3. + var value = keywordsProperty != null ? + keywordsProperty.GetValue(instance, null) as string[] : null; + return value ?? new string[] {}; + } + } + + /// + /// Get the package's unique name. + /// + public string Name { + get { return nameProperty.GetValue(instance, null) as string; } + } + + /// + /// Get the package ID. + /// + public string PackageId { + get { return packageIdProperty.GetValue(instance, null) as string; } + } + + /// + /// Get the resolved direct and indirect dependencies of this package. + /// + public ICollection ResolvedDependencies { + get { + return resolvedDependenciesProperty != null ? + (new CollectionWrapper( + resolvedDependenciesProperty.GetValue(instance, null) + as IEnumerable)).Collection : new DependencyInfo[] {}; + } + } + + /// + /// Get the package version. + /// + public string Version { + get { return versionProperty.GetValue(instance, null) as string; } + } + + /// + /// Get available versions of this package. + /// + public ICollection Versions { + get { + // Not available until Unity 2018. + return versionsProperty != null ? (new CollectionWrapper( + versionsProperty.GetValue(instance, null) as IEnumerable)).Collection : + new VersionsInfo[] {}; + } + } + + + /// + /// Convert to a string. + /// + public override string ToString() { + var components = new List(); + components.Add(String.Format("displayName: '{0}'", DisplayName)); + components.Add(String.Format("name: {0}", Name)); + components.Add(String.Format("packageId: {0}", PackageId)); + components.Add(String.Format("author: '{0}'", Author)); + components.Add(String.Format("version: {0}", Version)); + components.Add(String.Format("availableVersions: [{0}]", + ObjectCollectionToString(Versions, ", "))); + components.Add(String.Format("dependencies: [{0}]", + ObjectCollectionToString(Dependencies, ", "))); + components.Add(String.Format("resolvedDependencies: [{0}]", + ObjectCollectionToString(ResolvedDependencies, ", "))); + components.Add(String.Format("category: '{0}'", Category)); + components.Add(String.Format("keywords: [{0}]", + ObjectCollectionToString(Keywords, ", "))); + return String.Join(", ", components.ToArray()); + } + } + + /// + /// Wrapper for PackageManager.Requests.Request. + /// + private class Request : Wrapper { + + /// + /// PackageManager.Request type. + /// + private static Type type; + + /// + /// PackageManager.Request.Error property. + /// + private static PropertyInfo errorProperty; + + /// + /// PackageManager.Request.IsCompleted property. + /// + private static PropertyInfo isCompletedProperty; + + /// + /// Initialize the wrapper. + /// + /// Request instance to wrap. + public Request(object request) : base(request) { + type = type ?? + VersionHandler.FindClass("UnityEditor", + "UnityEditor.PackageManager.Requests.Request"); + if (type != null) { + errorProperty = errorProperty ?? type.GetProperty("Error"); + isCompletedProperty = isCompletedProperty ?? type.GetProperty("IsCompleted"); + } + } + + /// + /// Whether the request is complete. + /// + public bool IsComplete { + get { return (bool)isCompletedProperty.GetValue(instance, null); } + } + + /// + /// Error associated with the request (valid when IsComplete is true). + /// + public Error Error { + get { + var error = errorProperty.GetValue(instance, null); + return error != null ? new PackageManagerClient.Error(error) : null; + } + } + } + + /// + /// Request that wraps a class of type UnityEditor.PackageManager.Requests.typeName which + /// returns a collection of objects of type T. + /// + private class CollectionRequest : Request where T : Wrapper, new() { + + /// + /// UnityEditor.PackageManager.Requests.typeName class. + /// + private Type collectionRequestType; + + /// + /// PackageManager.Requests.typeName property. + /// + private PropertyInfo resultProperty; + + /// + /// Create a wrapper around UnityEditor.PackageManager.Requests.typeName. + /// + /// Object to wrap. + /// Name of the type under + /// UnityEditor.PackageManager.Requests to wrap. + public CollectionRequest(object request, string typeName) : base(request) { + collectionRequestType = + VersionHandler.FindClass("UnityEditor", + "UnityEditor.PackageManager.Requests." + typeName); + if (collectionRequestType != null) { + resultProperty = collectionRequestType.GetProperty("Result"); + } + } + + /// + /// Get the set of packages returned by the request. + /// + public ICollection Result { + get { + return (new CollectionWrapper(resultProperty.GetValue(instance, null) as + IEnumerable).Collection); + } + } + } + + /// + /// Wrapper for UnityEditor.PackageManager.Requests.AddRequest + /// + private class AddRequest : Request { + + /// + /// UnityEditor.PackageManager.Requests.AddRequest class. + /// + private static Type addRequestType; + + /// + /// PackageManager.Requests.AddRequest.Result property. + /// + private static PropertyInfo resultProperty; + + /// + /// Create a wrapper around AddRequest. + /// + /// Object to wrap. + public AddRequest(object request) : base(request) { + addRequestType = addRequestType ?? + VersionHandler.FindClass("UnityEditor", + "UnityEditor.PackageManager.Requests.AddRequest"); + if (addRequestType != null) { + resultProperty = resultProperty ?? addRequestType.GetProperty("Result"); + } + } + + /// + /// Get the installed package if successful, null otherwise. + /// + public PackageInfo Result { + get { + var result = resultProperty.GetValue(instance, null); + return result != null ? new PackageInfo(result) : null; + } + } + } + + /// + /// Wrapper for UnityEditor.PackageManager.Requests.RemoveRequest + /// + private class RemoveRequest : Request { + + /// + /// UnityEditor.PackageManager.Requests.RemoveRequest class. + /// + private static Type removeRequestType; + + /// + /// PackageManager.Requests.RemoveRequest.Result property. + /// + private static PropertyInfo resultProperty; + + /// + /// Create a wrapper around RemoveRequest. + /// + /// Object to wrap. + public RemoveRequest(object request) : base(request) { + removeRequestType = removeRequestType ?? + VersionHandler.FindClass("UnityEditor", + "UnityEditor.PackageManager.Requests.RemoveRequest"); + if (removeRequestType != null) { + resultProperty = resultProperty ?? removeRequestType.GetProperty("PackageIdOrName"); + } + } + + /// + /// Get the removed package if successful, null otherwise. + /// + public string Result { + get { + var result = resultProperty.GetValue(instance, null); + return result != null ? result as string : null; + } + } + } + + /// + /// Wrapper for UnityEditor.PackageManager.Requests.ListRequest + /// + private class ListRequest : CollectionRequest { + + /// + /// Create a wrapper around ListRequest. + /// + /// Object to wrap. + public ListRequest(object request) : base(request, "ListRequest") {} + } + + /// + /// Wrapper for UnityEditor.PackageManager.Requests.SearchRequest + /// + private class SearchRequest : CollectionRequest { + + /// + /// Create a wrapper around SearchRequest. + /// + /// Object to wrap. + public SearchRequest(object request) : base(request, "SearchRequest") {} + } + + /// + /// Wrapper for PackageManager.Client. + /// + private static class Client { + /// + /// PackageManager.Client static class. + /// + private static Type type; + + /// + /// Method to add a package. + /// + private static MethodInfo addMethod; + + /// + /// Method to remove a package. + /// + private static MethodInfo removeMethod; + + /// + /// Method to list packages. + /// + private static MethodInfo listMethod; + + /// + /// Method to list packages with an optional offline mode parameter. + /// + private static MethodInfo listMethodOfflineMode; + + /// + /// Method to search for a package. + /// + private static MethodInfo searchMethod; + + /// + /// Method to search for all available packages. + /// + private static MethodInfo searchAllMethod; + + /// + /// Cache the PackageManager.Client class and methods. + /// + static Client() { + type = type ?? + VersionHandler.FindClass("UnityEditor", "UnityEditor.PackageManager.Client"); + if (type != null) { + addMethod = addMethod ?? type.GetMethod("Add", new [] { typeof(String) }); + removeMethod = removeMethod ?? type.GetMethod("Remove", new [] { typeof(String) }); + listMethod = listMethod ?? type.GetMethod("List", Type.EmptyTypes); + listMethodOfflineMode = + listMethodOfflineMode ?? type.GetMethod("List", new [] { typeof(bool) }); + searchMethod = searchMethod ?? type.GetMethod("Search", new [] { typeof(String) }); + searchAllMethod = searchAllMethod ?? type.GetMethod("SearchAll", Type.EmptyTypes); + } + } + + /// + /// Determine Whether the package manager is available. + /// + public static bool Available { + get { + // The current set of methods are checked for as this provides a baseline set + // of functionality for listing, searching and adding / removing packages across + // all versions of Unity since the package manager was introduced. + // listMethodOfflineMode is available in Unity 2018 and above, + // listMethod is available in Unity 2017.x so always utilize the behavior from + // 2017 (i.e no offline queries). + return type != null && addMethod != null && removeMethod != null && + (listMethod != null || listMethodOfflineMode != null) && searchMethod != null; + } + } + + /// + /// Add a package. + /// + /// Name of the package to add. + /// Request object to monitor progress. + public static AddRequest Add(string packageIdOrName) { + return new AddRequest(addMethod.Invoke(null, new object[] { packageIdOrName })); + } + + /// + /// Remove a package. + /// + /// Name of the package to add. + /// Request object to monitor progress. + public static RemoveRequest Remove(string packageIdOrName) { + return new RemoveRequest(removeMethod.Invoke(null, new object[] { packageIdOrName })); + } + + /// + /// List packages available to install. + /// + public static ListRequest List() { + object request = listMethodOfflineMode != null ? + listMethodOfflineMode.Invoke(null, new object[] { false }) : + listMethod.Invoke(null, null); + return new ListRequest(request); + } + + /// + /// Search for an available package. + /// + /// Name of the package to add. + /// Request object to monitor progress. + public static SearchRequest Search(string packageIdOrName) { + return new SearchRequest(searchMethod.Invoke(null, new object[] { packageIdOrName })); + } + + /// + /// Search for all available packages. + /// + /// Request object to monitor progress or null if SearchAll() isn't + /// available. + public static SearchRequest SearchAll() { + return searchAllMethod != null ? + new SearchRequest(searchAllMethod.Invoke(null, new object[] {})) : null; + } + } + + /// + /// Enumerates through a set of items, optionally reporting progress. + /// + private class EnumeratorProgressReporter { + /// + /// Enumerator of packages to search for. + /// + private IEnumerator enumerator; + + /// + /// Called as the search progresses. + /// + private Action progress = null; + + /// + /// Number of items to search. + /// + private int itemCount; + + /// + /// Number of items searched. + /// + private int itemIndex = 0; + + /// + /// Construct the instance. + /// + /// Items to iterate through. + /// Reports progress as iteration proceeds. + public EnumeratorProgressReporter(ICollection items, + Action progressReporter) { + itemCount = items.Count; + enumerator = items.GetEnumerator(); + progress = progressReporter; + } + + /// + /// Report progress. + /// + /// Progress through the operation. + /// Item being worked on. + protected void ReportProgress(float itemProgress, string item) { + if (progress != null) { + try { + progress(itemProgress, item); + } catch (Exception e) { + PackageManagerClient.Logger.Log( + String.Format("Progress reporter raised exception {0}", e), + level: LogLevel.Error); + } + } + } + + /// + /// Get the next item. + /// + protected string NextItem() { + if (!enumerator.MoveNext()) { + ReportProgress(1.0f, ""); + return null; + } + var item = enumerator.Current; + ReportProgress((float)itemIndex / (float)itemCount, item); + itemIndex++; + return item; + } + } + + /// + /// Result of a package remove operation. + /// + public class RemoveResult { + + /// + /// Package ID involved in the remove operation. + /// + public string PackageId { get; set; } + + /// + /// Error. + /// + public Error Error { get; set; } + + /// + /// Construct an empty result. + /// + public RemoveResult() { + this.PackageId = ""; + this.Error = new Error(null); + } + } + + /// + /// Result of a package add / install operation. + /// + public class InstallResult { + + /// + /// Information about the installed package, null if no package is installed. + /// + public PackageInfo Package { get; set; } + + /// + /// Error. + /// + public Error Error { get; set; } + + /// + /// Construct an empty result. + /// + public InstallResult() { + this.Package = null; + this.Error = new Error(null); + } + } + + /// + /// Adds a set of packages to the project. + /// + private class PackageInstaller : EnumeratorProgressReporter { + + /// + /// Pending add request. + /// + private AddRequest request = null; + + /// + /// Result of package installation. + /// + private Dictionary installed = + new Dictionary(); + + /// + /// Called when the operation is complete. + /// + Action> complete; + + /// + /// Install a set of packages using the package manager. + /// + /// Package IDs or names to search for. + /// Called when the operation is complete. + /// Reports progress through the search. + public PackageInstaller(ICollection packageIdsOrNames, + Action> complete, + Action progress = null) : + base(packageIdsOrNames, progress) { + this.complete = complete; + InstallNext(); + } + + /// + /// Install the next package. + /// + private void InstallNext() { + var packageIdOrName = NextItem(); + if (String.IsNullOrEmpty(packageIdOrName)) { + var completion = complete; + complete = null; + completion(installed); + return; + } + request = Client.Add(packageIdOrName); + RunOnMainThread.PollOnUpdateUntilComplete(() => { + if (!request.IsComplete) return false; + if (complete == null) return true; + installed[packageIdOrName] = new InstallResult() { + Package = request.Result, + Error = request.Error ?? new Error(null) + }; + RunOnMainThread.Run(() => { InstallNext(); }); + return true; + }); + } + } + + /// + /// Result of a search operation. + /// + public class SearchResult { + + /// + /// Packages found. + /// + public ICollection Packages { get; set; } + + /// + /// Error. + /// + public Error Error { get; set; } + + /// + /// Construct an empty result. + /// + public SearchResult() { + Packages = new List(); + Error = new Error(null); + } + } + + /// + /// Searches for packages by name. + /// + private class PackageSearcher : EnumeratorProgressReporter { + + /// + /// Pending search request. + /// + private SearchRequest request = null; + + /// + /// Packages found by the search. + /// + private Dictionary found = new Dictionary(); + + /// + /// Called when the operation is complete. + /// + private Action> complete; + + /// + /// Search for a set of packages in the package manager. + /// + /// Package IDs or names to search for. + /// Called when the operation is complete. + /// Reports progress through the search. + public PackageSearcher(ICollection packageIdsOrNames, + Action> complete, + Action progress = null) : + base(packageIdsOrNames, progress){ + this.complete = complete; + SearchNext(); + } + + /// + /// Perform the next search operation. + /// + private void SearchNext() { + var packageIdOrName = NextItem(); + if (String.IsNullOrEmpty(packageIdOrName)) { + var completion = complete; + complete = null; + completion(found); + return; + } + + request = Client.Search(packageIdOrName); + RunOnMainThread.PollOnUpdateUntilComplete(() => { + if (!request.IsComplete) return false; + if (complete == null) return true; + var packages = new List(); + var result = request.Result; + if (request.Result != null) packages.AddRange(result); + found[packageIdOrName] = new SearchResult() { + Packages = packages, + Error = request.Error ?? new Error(null) + }; + RunOnMainThread.Run(() => { SearchNext(); }); + return true; + }); + } + } + + /// + /// Logger for this class. + /// + public static Logger Logger = PackageManagerResolver.logger; + + /// + /// Job queue for package managers jobs. + /// + /// + /// PackageManager.Client operations are not thread-safe, each operation needs to be + /// executed sequentially so this class simplifies the process of scheduling operations on + /// the main thread. + /// + private static RunOnMainThread.JobQueue jobQueue = new RunOnMainThread.JobQueue(); + + /// + /// Determine Whether the package manager is available. + /// + public static bool Available { get { return Client.Available; } } + + /// + /// Add a package to the project. + /// + /// ID or name of the package to add. + /// Called when the operation is complete. + public static void AddPackage(string packageIdOrName, Action complete) { + if (!Available) { + complete(new InstallResult()); + return; + } + jobQueue.Schedule(() => { + new PackageInstaller(new [] { packageIdOrName }, + (result) => { + jobQueue.Complete(); + complete(result[packageIdOrName]); + }, + progress: null); + }); + } + + /// + /// Add packages to the project. + /// + /// IDs or names of the packages to add. + /// Called when the operation is complete. + /// Reports progress through the installation. + public static void AddPackages(ICollection packageIdsOrNames, + Action> complete, + Action progress = null) { + if (!Client.Available) { + if (progress != null) progress(1.0f, ""); + complete(new Dictionary()); + return; + } + jobQueue.Schedule(() => { + new PackageInstaller(packageIdsOrNames, + (result) => { + jobQueue.Complete(); + complete(result); + }, + progress: progress); + }); + } + + /// + /// Remove a package from the project. + /// + /// ID or name of the package to add. + /// Called when the operation is complete. + public static void RemovePackage(string packageIdOrName, Action complete) { + if (!Available) { + complete(new RemoveResult()); + return; + } + jobQueue.Schedule(() => { + var result = Client.Remove(packageIdOrName); + RunOnMainThread.PollOnUpdateUntilComplete(() => { + if (!result.IsComplete) return false; + jobQueue.Complete(); + complete(new RemoveResult() { + PackageId = result.Result ?? packageIdOrName, + Error = result.Error ?? new Error(null) + }); + return true; + }); + }); + } + + /// + /// List all packages that the project current depends upon. + /// + /// Action that is called with the list of packages in the + /// project. + public static void ListInstalledPackages(Action complete) { + if (!Available) { + complete(new SearchResult()); + return; + } + jobQueue.Schedule(() => { + var request = Client.List(); + RunOnMainThread.PollOnUpdateUntilComplete(() => { + if (!request.IsComplete) return false; + jobQueue.Complete(); + complete(new SearchResult() { + Packages = request.Result, + Error = request.Error ?? new Error(null) + }); + return true; + }); + }); + } + + /// + /// Search for all packages available for installation in the package manager. + /// + /// Action that is called with the list of packages available for + /// installation. + public static void SearchAvailablePackages(Action complete) { + jobQueue.Schedule(() => { + var request = Client.SearchAll(); + if (request == null) { + jobQueue.Complete(); + complete(new SearchResult()); + return; + } + RunOnMainThread.PollOnUpdateUntilComplete(() => { + if (!request.IsComplete) return false; + jobQueue.Complete(); + complete(new SearchResult() { + Packages = request.Result, + Error = request.Error ?? new Error(null) + }); + return true; + }); + }); + } + + /// + /// Search for a set of packages in the package manager. + /// + /// Packages to search for. + /// Action that is called with a collection of available packages that + /// is a result of each search string. + /// Reports progress through the search. + public static void SearchAvailablePackages( + ICollection packageIdsOrNames, + Action> complete, + Action progress = null) { + if (!Available) { + if (progress != null) progress(1.0f, ""); + complete(new Dictionary()); + return; + } + jobQueue.Schedule(() => { + new PackageSearcher(packageIdsOrNames, + (result) => { + jobQueue.Complete(); + complete(result); + }, + progress: progress); + }); + } +} + +} diff --git a/source/PackageManagerResolver/src/PackageManagerRegistry.cs b/source/PackageManagerResolver/src/PackageManagerRegistry.cs new file mode 100644 index 00000000..d62db6e0 --- /dev/null +++ b/source/PackageManagerResolver/src/PackageManagerRegistry.cs @@ -0,0 +1,152 @@ +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Google { + using System; + using System.Collections.Generic; + + /// + /// UPM/NPM package registry. + /// + internal class PackageManagerRegistry { + + /// + /// Construct an empty registry. + /// + public PackageManagerRegistry() { + Scopes = new List(); + } + + /// + /// Registry name. + /// + public string Name; + + /// + /// Registry URL. + /// + public string Url; + + /// + /// Scopes for this registry. + /// See https://docs.unity3d.com/Manual/upm-scoped.html and + /// https://docs.npmjs.com/using-npm/scope.html + /// + public List Scopes; + + /// + /// Terms of service for the registry. + /// + public string TermsOfService; + + /// + /// Privacy policy for the registry. + /// + public string PrivacyPolicy; + + /// + /// Tag that indicates where this was created. + /// + /// + /// This can be used to display which file this was read from or where in code this + /// registry was created. + /// + public string CreatedBy = System.Environment.StackTrace; + + /// + /// Arbitrary custom runtime data. + /// + /// + /// For example, this can be used associate this instance with the Dictionary representation + /// parsed from a JSON file. Storing the parsed JSON Dictionary it's possible to easily + /// remove object from the JSON document without requiring a separate data structure + /// (e.g a map) to associate the two data representations. + /// + public object CustomData; + + /// + /// Convert to a human readable string excluding the TermsOfService and CreatedBy fields. + /// + /// String representation of this instance. + public override string ToString() { + return String.Format("name: {0}, url: {1}, scopes: {2}", + Name, Url, + Scopes != null ? + String.Format("[{0}]", String.Join(", ", Scopes.ToArray())) : + "[]"); + } + + /// + /// Compare with this object. + /// + /// Object to compare with. + /// true if both objects have the same contents excluding CreatedBy, + /// false otherwise. + public override bool Equals(System.Object obj) { + var other = obj as PackageManagerRegistry; + return other != null && + Name == other.Name && + Url == other.Url && + TermsOfService == other.TermsOfService && + PrivacyPolicy == other.PrivacyPolicy && + Scopes != null && other.Scopes != null && + (new HashSet(Scopes)).SetEquals(other.Scopes) && + CustomData == other.CustomData; + } + + /// + /// Generate a hash of this object excluding CreatedBy. + /// + /// Hash of this object. + public override int GetHashCode() { + int hash = 0; + if (!String.IsNullOrEmpty(Name)) hash ^= Name.GetHashCode(); + if (!String.IsNullOrEmpty(Url)) hash ^= Url.GetHashCode(); + if (!String.IsNullOrEmpty(TermsOfService)) hash ^= TermsOfService.GetHashCode(); + if (!String.IsNullOrEmpty(PrivacyPolicy)) hash ^= PrivacyPolicy.GetHashCode(); + if (Scopes != null) { + foreach (var scope in Scopes) { + hash ^= scope.GetHashCode(); + } + } + if (CustomData != null) hash ^= CustomData.GetHashCode(); + return hash; + } + + /// + /// Convert a list of PackageManagerRegistry instances to a list of strings. + /// + /// List of registries to convert to strings. + /// List of strings. + public static List ToStringList( + IEnumerable registries) { + var registryStrings = new List(); + foreach (var registry in registries) { + registryStrings.Add(registry.ToString()); + } + return registryStrings; + } + + /// + /// Convert a list of PackageManagerRegistry instance to a newline separated string. + /// + /// List of registries to convert to strings. + /// String representation of the list. + public static string ToString(IEnumerable registries) { + return String.Join("\n", ToStringList(registries).ToArray()); + } + } +} diff --git a/source/PackageManagerResolver/src/PackageManagerResolver.cs b/source/PackageManagerResolver/src/PackageManagerResolver.cs new file mode 100644 index 00000000..d041cc26 --- /dev/null +++ b/source/PackageManagerResolver/src/PackageManagerResolver.cs @@ -0,0 +1,665 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using UnityEngine; +using UnityEditor; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Google { + +[InitializeOnLoad] +public class PackageManagerResolver : AssetPostprocessor { + /// + /// A unique class to create the multi-select window to add registries. + /// + class PackageManagerResolverWindow : MultiSelectWindow {} + + /// + /// Name of the plugin. + /// + internal const string PLUGIN_NAME = "Package Manager Resolver"; + + /// + /// The operation to perform when modifying the manifest. + /// + internal enum ManifestModificationMode { + /// + /// Add package registries that are not in the manifest. + /// + Add, + + /// + /// Remove package registries from the manifest that are in the loaded config. + /// + Remove, + + /// + /// Display and add / remove all package registries from the manifest. + /// + Modify, + } + + private const string ADD_REGISTRIES_QUESTION = + "Add the selected Package Manager registries to your project?"; + private const string REMOVE_REGISTRIES_QUESTION = + "Remove the selected Package Manager registries from your project?"; + private const string ADD_REGISTRIES_DESCRIPTION = + "Adding a registry will allow you to install, upgrade and remove packages from the " + + "registry's server in the Package Manager. By adding the selected registries, you " + + "agree that your use of these registries are subject to their terms of service and you " + + "acknowledge that data will be collected in accordance with each registry's privacy " + + "policy."; + private const string REMOVE_REGISTRIES_DESCRIPTION = + "Removing a registry will prevent you from installing and upgrading packages from the " + + "registry's server in the Package Manager. It will not remove packages from the " + + "registry's server that are already installed"; + private const string ADD_OR_REMOVE_REGISTRIES_QUESTION = + "Add the selected Package Manager registries to and remove the " + + "unselected registries from your project?"; + private const string MODIFY_MENU_ITEM_DESCRIPTION = + "You can always add or remove registries at a later time using menu item:\n" + + "'Assets > External Dependency Manager > Package Manager Resolver > " + + "Modify Registries'."; + + /// + /// Scroll location of the manifest view on the left in the registry selection window. + /// + private static Vector2 scrollManifestViewLeft; + + /// + /// Scroll location of the manifest view on the right in the registry selection window. + /// + private static Vector2 scrollManifestViewRight; + + /// + /// Enables / disables external package registries for Package Manager. + /// + static PackageManagerResolver() { + logger.Log("Loaded PackageManagerResolver", level: LogLevel.Verbose); + + RunOnMainThread.Run(() => { + // Load log preferences. + UpdateLoggerLevel(VerboseLoggingEnabled); + }, runNow: false); + } + + /// + /// Display documentation. + /// + [MenuItem("Assets/External Dependency Manager/Package Manager Resolver/Documentation")] + public static void ShowDocumentation() { + analytics.OpenUrl(VersionHandlerImpl.DocumentationUrl( + "#package-manager-resolver-usage"), "Usage"); + } + + /// + /// Add the settings dialog for this module to the menu and show the + /// window when the menu item is selected. + /// + [MenuItem("Assets/External Dependency Manager/Package Manager Resolver/Settings")] + public static void ShowSettings() { + PackageManagerResolverSettingsDialog window = + (PackageManagerResolverSettingsDialog)EditorWindow.GetWindow( + typeof(PackageManagerResolverSettingsDialog), true, PLUGIN_NAME + " Settings"); + window.Initialize(); + window.Show(); + } + + /// + /// Check registry status based on current settings. + /// + internal static void CheckRegistries() { + if (Enable) { + UpdateManifest(ManifestModificationMode.Add, + promptBeforeAction: PromptToAddRegistries, + showDisableButton: true); + } + } + + + /// + /// Called by Unity when all assets have been updated and checks to see whether any registries + /// have changed. + /// + /// Imported assets. + /// Deleted assets. + /// Moved assets. + /// Moved from asset paths. (unused) + private static void OnPostprocessAllAssets(string[] importedAssets, + string[] deletedAssets, + string[] movedAssets, + string[] movedFromAssetPaths) { + if (!Enable) return; + bool registriesChanged = false; + var checkAssets = new List(importedAssets); + checkAssets.AddRange(movedAssets); + foreach (var asset in checkAssets) { + if (XmlPackageManagerRegistries.IsRegistriesFile(asset)) { + registriesChanged = true; + break; + } + AssetImporter importer = AssetImporter.GetAtPath(asset); + if (importer != null) { + foreach (var assetLabel in AssetDatabase.GetLabels(importer)) { + if (assetLabel == XmlPackageManagerRegistries.REGISTRIES_LABEL) { + registriesChanged = true; + break; + } + } + } + } + if (registriesChanged) CheckRegistries(); + } + + /// + /// Add registries in the XML configuration to the project manifest. + /// + [MenuItem("Assets/External Dependency Manager/Package Manager Resolver/Add Registries")] + public static void AddRegistries() { + UpdateManifest(ManifestModificationMode.Add, promptBeforeAction: true, + showDisableButton: false); + } + + /// + /// Remove registries in the XML configuration from the project manifest. + /// + [MenuItem("Assets/External Dependency Manager/Package Manager Resolver/Remove Registries")] + public static void RemoveRegistries() { + UpdateManifest(ManifestModificationMode.Remove, promptBeforeAction: true, + showDisableButton: false); + } + + /// + /// Add or remove registries in the project manifest based upon the set available in the XML + /// configuration. + /// + [MenuItem("Assets/External Dependency Manager/Package Manager Resolver/Modify Registries")] + public static void ModifyRegistries() { + UpdateManifest(ManifestModificationMode.Modify, promptBeforeAction: true, + showDisableButton: false); + } + + /// + /// Read registries from XML configuration files. + /// + /// Dictionary of registries indexed by URL. + private static Dictionary ReadRegistriesFromXml() { + // Read registries from XML files. + var xmlReader = new XmlPackageManagerRegistries(); + xmlReader.ReadAll(logger); + return xmlReader.Registries; + } + + /// + /// Apply registry changes to the modifier. + /// + /// Object that modifies the project's manifest. + /// Registries added to the modifier. + /// Registries removed from the modifier. + /// Registries that are available in the + /// configuration. + /// Registries that are present in the manifest. + /// URLs of selected registries, these should be items in + /// availableRegistries. + /// Whether to add selected registries to the manifest. + /// Whether to remove unselected registries from the + /// manifest. + /// If false, adds the selected registries and removes the + /// unselected registries. If true, removes the selected registries and adds the unselected + /// registries. + /// true if the manifest is modified. + private static bool SyncRegistriesToModifier( + PackageManifestModifier manifestModifier, + out List registriesToAdd, + out List registriesToRemove, + Dictionary availableRegistries, + Dictionary> manifestRegistries, + HashSet selectedRegistryUrls, + bool addRegistries = true, + bool removeRegistries = true, + bool invertSelection = false) { + // Build a list of registries to add to and remove from the modifier. + registriesToAdd = new List(); + registriesToRemove = new List(); + + foreach (var availableRegistry in availableRegistries.Values) { + var url = availableRegistry.Url; + bool isSelected = selectedRegistryUrls.Contains(url); + if (invertSelection) isSelected = !isSelected; + + bool currentlyInManifest = manifestRegistries.ContainsKey(url); + + if (isSelected) { + if (addRegistries && !currentlyInManifest) { + registriesToAdd.Add(availableRegistry); + } + } else { + if (removeRegistries && currentlyInManifest) { + registriesToRemove.Add(availableRegistry); + } + } + } + + bool manifestModified = false; + if (registriesToAdd.Count > 0) { + manifestModifier.AddRegistries(registriesToAdd); + manifestModified = true; + } + if (registriesToRemove.Count > 0) { + manifestModifier.RemoveRegistries(registriesToRemove); + manifestModified = true; + } + return manifestModified; + } + + /// + /// Apply registry changes to the projects manifest. + /// + /// Object that modifies the project's manifest. + /// Registries that are available in the + /// configuration. + /// Registries that are present in the manifest. + /// URLs of selected registries, these should be items in + /// availableRegistries. + /// Whether to add selected registries to the manifest. + /// Whether to remove unselected registries from the + /// manifest. + /// If false, adds the selected registries and removes the + /// unselected registries. If true, removes the selected registries and adds the unselected + /// registries. + /// If specified, is extended with the list of registries added + /// to the manifest. + /// true if successful, false otherwise. + private static bool SyncRegistriesToManifest( + PackageManifestModifier manifestModifier, + Dictionary availableRegistries, + Dictionary> manifestRegistries, + HashSet selectedRegistryUrls, + bool addRegistries = true, + bool removeRegistries = true, + bool invertSelection = false, + List addedRegistries = null) { + List registriesToAdd; + List registriesToRemove; + bool manifestModified = SyncRegistriesToModifier( + manifestModifier, out registriesToAdd, out registriesToRemove, + availableRegistries, manifestRegistries, selectedRegistryUrls, + addRegistries, removeRegistries, invertSelection); + + bool successful = true; + if (manifestModified) { + successful = manifestModifier.WriteManifest(); + if (successful) { + if (registriesToAdd.Count > 0) { + logger.Log(String.Format( + "Added registries to {0}:\n{1}", + PackageManifestModifier.MANIFEST_FILE_PATH, + PackageManagerRegistry.ToString(registriesToAdd))); + if (addedRegistries != null) addedRegistries.AddRange(registriesToAdd); + } + if (registriesToRemove.Count > 0) { + logger.Log(String.Format( + "Removed registries from {0}:\n{1}", + PackageManifestModifier.MANIFEST_FILE_PATH, + PackageManagerRegistry.ToString(registriesToRemove))); + } + analytics.Report( + "registry_manifest/write/success", + new KeyValuePair[] { + new KeyValuePair("added", registriesToAdd.Count.ToString()), + new KeyValuePair("removed", + registriesToRemove.Count.ToString()) + }, + "Project Manifest Modified"); + } else { + analytics.Report("registry_manifest/write/failed", "Project Manifest Write Failed"); + } + } + return successful; + } + + /// + /// Update manifest file based on the settings. + /// + /// Manifest modification mode being applied. + /// Whether to display a window that prompts the user for + /// confirmation before applying changes. + /// Whether to show a button to disable auto-registry + /// addition. + /// List of scope prefixes used to filter the set of registries + /// being operated on. + internal static void UpdateManifest(ManifestModificationMode mode, + bool promptBeforeAction = true, + bool showDisableButton = false, + IEnumerable scopePrefixFilter = null) { + if (!ScopedRegistriesSupported) { + logger.Log(String.Format("Scoped registries not supported in this version of Unity."), + level: LogLevel.Verbose); + return; + } + + PackageManifestModifier modifier = new PackageManifestModifier() { Logger = logger }; + Dictionary> manifestRegistries = + modifier.ReadManifest() ? modifier.PackageManagerRegistries : null; + if (manifestRegistries == null) { + PackageManagerResolver.analytics.Report( + "registry_manifest/read/failed", + "Update Manifest failed: Read/Parse manifest failed"); + return; + } + + var xmlRegistries = ReadRegistriesFromXml(); + + if (xmlRegistries.Count == 0) { + logger.Log("No registry found from any Registries.xml files", level: LogLevel.Warning); + } + + // Filter registries using the scope prefixes. + if (scopePrefixFilter != null) { + foreach (var registry in new List(xmlRegistries.Values)) { + bool removeRegistry = true; + foreach (var scope in registry.Scopes) { + foreach (var scopePrefix in scopePrefixFilter) { + if (scope.StartsWith(scopePrefix)) { + removeRegistry = false; + } + } + } + if (removeRegistry) xmlRegistries.Remove(registry.Url); + } + } + + // Filter the set of considered registries based upon the modification mode. + HashSet selectedRegistryUrls = null; + switch (mode) { + case ManifestModificationMode.Add: + // Remove all items from the XML loaded registries that are present in the manifest. + foreach (var url in manifestRegistries.Keys) xmlRegistries.Remove(url); + selectedRegistryUrls = new HashSet(xmlRegistries.Keys); + break; + case ManifestModificationMode.Remove: + // Remove all items from the XML loaded registries that are not present in the + // manifest. + foreach (var url in new List(xmlRegistries.Keys)) { + if (!manifestRegistries.ContainsKey(url)) { + xmlRegistries.Remove(url); + } + } + selectedRegistryUrls = new HashSet(xmlRegistries.Keys); + break; + case ManifestModificationMode.Modify: + selectedRegistryUrls = new HashSet(); + // Keep all XML loaded registries and select the items in the manifest. + foreach (var url in xmlRegistries.Keys) { + if (manifestRegistries.ContainsKey(url)) { + selectedRegistryUrls.Add(url); + } + } + break; + } + + // Applies the manifest modification based upon the modification mode. + Action> syncRegistriesToManifest = (urlSelectionToApply) => { + var addedRegistries = new List(); + SyncRegistriesToManifest(modifier, xmlRegistries, manifestRegistries, + urlSelectionToApply, + addRegistries: (mode == ManifestModificationMode.Add || + mode == ManifestModificationMode.Modify), + removeRegistries: (mode == ManifestModificationMode.Remove || + mode == ManifestModificationMode.Modify), + invertSelection: mode == ManifestModificationMode.Remove, + addedRegistries: addedRegistries); + // If any registries were added try migration if enabled. + if (addedRegistries.Count > 0 && PromptToMigratePackages) { + PackageMigrator.MigratePackages(); + } + }; + + // Get the manifest json string based on the current selection and mode. + Func, string> getManifestJsonAfterChange = (urlSelectionToApply) => { + PackageManifestModifier clonedModifier = new PackageManifestModifier(modifier); + List toAdd; + List toRemove; + SyncRegistriesToModifier(clonedModifier, out toAdd, out toRemove, + xmlRegistries, manifestRegistries, + urlSelectionToApply, + addRegistries: (mode == ManifestModificationMode.Add || + mode == ManifestModificationMode.Modify), + removeRegistries: (mode == ManifestModificationMode.Remove || + mode == ManifestModificationMode.Modify), + invertSelection: mode == ManifestModificationMode.Remove); + return clonedModifier.GetManifestJson(); + }; + + if (xmlRegistries.Count > 0) { + if (promptBeforeAction) { + // Build a list of items to display. + var registryItems = new List>(); + foreach (var kv in xmlRegistries) { + registryItems.Add(new KeyValuePair(kv.Key, kv.Value.Name)); + } + + // Optional when prompting is enabled or forced. + var window = + MultiSelectWindow.CreateMultiSelectWindow( + PLUGIN_NAME); + window.minSize = new Vector2(1024, 500); + window.AvailableItems = registryItems; + window.Sort(1); + window.SelectedItems = selectedRegistryUrls; + switch (mode) { + case ManifestModificationMode.Add: + window.Caption = String.Format("{0}\n\n{1}\n\n{2}", + ADD_REGISTRIES_QUESTION, + ADD_REGISTRIES_DESCRIPTION, + MODIFY_MENU_ITEM_DESCRIPTION); + window.ApplyLabel = "Add Selected Registries"; + break; + case ManifestModificationMode.Remove: + window.Caption = String.Format("{0}\n\n{1}{2}", + REMOVE_REGISTRIES_QUESTION, + REMOVE_REGISTRIES_DESCRIPTION, + MODIFY_MENU_ITEM_DESCRIPTION); + window.ApplyLabel = "Remove Selected Registries"; + break; + case ManifestModificationMode.Modify: + window.Caption = String.Format("{0}\n\n{1} {2}", + ADD_OR_REMOVE_REGISTRIES_QUESTION, + ADD_REGISTRIES_DESCRIPTION, + REMOVE_REGISTRIES_DESCRIPTION); + window.ApplyLabel = "Modify Registries"; + break; + } + window.RenderItem = (item) => { + var registry = xmlRegistries[item.Key]; + var termsOfService = registry.TermsOfService; + if (!String.IsNullOrEmpty(termsOfService)) { + if (GUILayout.Button("View Terms of Service")) { + Application.OpenURL(termsOfService); + } + } + var privacyPolicy = registry.PrivacyPolicy; + if (!String.IsNullOrEmpty(privacyPolicy)) { + if (GUILayout.Button("View Privacy Policy")) { + Application.OpenURL(privacyPolicy); + } + } + }; + // Set the scroll position to the bottom since "scopedRegistry" section is most + // likely at the bottom of the file. + scrollManifestViewLeft = new Vector2(0.0f, float.PositiveInfinity); + scrollManifestViewRight = new Vector2(0.0f, float.PositiveInfinity); + + // Render the change in manifest.json dynamically. + window.RenderAfterItems = () => { + GUILayout.Label("Changes to Packages/manifest.json"); + EditorGUILayout.Space(); + + EditorGUILayout.BeginHorizontal(); + + EditorGUILayout.BeginVertical(); + GUILayout.Label("Before", EditorStyles.boldLabel); + EditorGUILayout.Space(); + scrollManifestViewLeft = + EditorGUILayout.BeginScrollView(scrollManifestViewLeft, + GUILayout.MaxWidth(window.position.width / 2.0f)); + EditorGUILayout.TextArea(modifier.GetManifestJson()); + EditorGUILayout.EndScrollView(); + EditorGUILayout.EndVertical(); + + EditorGUILayout.Space(); + + EditorGUILayout.BeginVertical(); + GUILayout.Label("After", EditorStyles.boldLabel); + EditorGUILayout.Space(); + scrollManifestViewRight = + EditorGUILayout.BeginScrollView(scrollManifestViewRight, + GUILayout.MaxWidth(window.position.width / 2.0f)); + EditorGUILayout.TextArea(getManifestJsonAfterChange(window.SelectedItems)); + EditorGUILayout.EndScrollView(); + EditorGUILayout.EndVertical(); + + EditorGUILayout.EndHorizontal(); + }; + if (showDisableButton) { + window.RenderBeforeCancelApply = () => { + if (GUILayout.Button("Disable Registry Addition")) { + Enable = false; + window.Close(); + } + }; + } + window.OnApply = () => { syncRegistriesToManifest(window.SelectedItems); }; + window.Show(); + } else { + syncRegistriesToManifest(selectedRegistryUrls); + } + } + } + + /// + /// Reset settings of this plugin to default values. + /// + internal static void RestoreDefaultSettings() { + settings.DeleteKeys(PreferenceKeys); + analytics.RestoreDefaultSettings(); + } + + // Keys in the editor preferences which control the behavior of this + // module. + private const string PreferenceEnable = + "Google.PackageManagerResolver.Enable"; + private const string PreferencePromptToAddRegistries = + "Google.PackageManagerResolver.PromptToAddRegistries"; + private const string PreferencePromptToMigratePackages = + "Google.PackageManagerResolver.PromptToMigratePackages"; + private const string PreferenceVerboseLoggingEnabled = + "Google.PackageManagerResolver.VerboseLoggingEnabled"; + // List of preference keys, used to restore default settings. + private static string[] PreferenceKeys = new[] { + PreferenceEnable, + PreferencePromptToAddRegistries, + PreferencePromptToMigratePackages, + PreferenceVerboseLoggingEnabled + }; + + // Unity started supporting scoped registries from 2018.4. + internal const float MinimumUnityVersionFloat = 2018.4f; + internal const string MinimumUnityVersionString = "2018.4"; + + // Settings used by this module. + internal static ProjectSettings settings = + new ProjectSettings("Google.PackageManagerResolver."); + + /// + /// Logger for this module. + /// + internal static Logger logger = new Logger(); + + // Analytics reporter. + internal static EditorMeasurement analytics = + new EditorMeasurement(settings, logger, VersionHandlerImpl.GA_TRACKING_ID, + VersionHandlerImpl.MEASUREMENT_ID, + VersionHandlerImpl.PLUGIN_SUITE_NAME, "", + VersionHandlerImpl.PRIVACY_POLICY) { + BasePath = "/upmresolver/", + BaseQuery = + String.Format("version={0}", PackageManagerResolverVersionNumber.Value.ToString()), + BaseReportName = "Package Manager Resolver: ", + InstallSourceFilename = Assembly.GetAssembly(typeof(PackageManagerResolver)).Location, + DataUsageUrl = VersionHandlerImpl.DATA_USAGE_URL + }; + + /// + /// Whether to use project level settings. + /// + public static bool UseProjectSettings { + get { return settings.UseProjectSettings; } + set { settings.UseProjectSettings = value; } + } + + /// + /// Enable / disable management of external registries. + /// + public static bool Enable { + get { return settings.GetBool(PreferenceEnable, defaultValue: true); } + set { settings.SetBool(PreferenceEnable, value); } + } + + /// + /// Enable / disable prompting the user to add registries. + /// + public static bool PromptToAddRegistries { + get { return settings.GetBool(PreferencePromptToAddRegistries, defaultValue: true); } + set { settings.SetBool(PreferencePromptToAddRegistries, value); } + } + + /// + /// Enable / disable prompting the user to migrate Version Handler to UPM packages after a + /// registry has been added. + /// + public static bool PromptToMigratePackages { + get { return settings.GetBool(PreferencePromptToMigratePackages, defaultValue: true); } + set { settings.SetBool(PreferencePromptToMigratePackages, value); } + } + + /// + /// Enable / disable verbose logging. + /// + public static bool VerboseLoggingEnabled { + get { return settings.GetBool(PreferenceVerboseLoggingEnabled, defaultValue: false); } + set { + settings.SetBool(PreferenceVerboseLoggingEnabled, value); + UpdateLoggerLevel(value); + } + } + + private static void UpdateLoggerLevel(bool verboseLoggingEnabled) { + logger.Level = verboseLoggingEnabled ? LogLevel.Verbose : LogLevel.Info; + } + + /// + /// Whether scoped registries are supported in current Unity editor. + /// + public static bool ScopedRegistriesSupported { + get { + return VersionHandler.GetUnityVersionMajorMinor() >= MinimumUnityVersionFloat; + } + } +} +} // namespace Google diff --git a/source/PackageManagerResolver/src/PackageManifestModifier.cs b/source/PackageManagerResolver/src/PackageManifestModifier.cs new file mode 100644 index 00000000..ac91099f --- /dev/null +++ b/source/PackageManagerResolver/src/PackageManifestModifier.cs @@ -0,0 +1,331 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using EDMInternal.MiniJSON; +using System; +using System.IO; +using System.Collections.Generic; + +namespace Google { + +internal class PackageManifestModifier { + + /// + /// Thrown if an error occurs while parsing the manifest. + /// + internal class ParseException : Exception { + + /// + /// Construct an exception with a message. + /// + public ParseException(string message) : base(message) {} + } + + /// + /// Relative location of the manifest file from project root. + /// + internal const string MANIFEST_FILE_PATH = "Packages/manifest.json"; + + /// + /// JSON keys to be used in manifest.json + /// manifest.json expects scoped registries to be specified in the following format: + /// { + /// "scopedRegistries" : [ + /// { + /// "name": "Registry Name", + /// "url": "https://path/to/registry", + /// "scopes": [ "com.company.scope" ] + /// } + /// ] + /// } + /// + private const string MANIFEST_SCOPED_REGISTRIES_KEY = "scopedRegistries"; + private const string MANIFEST_REGISTRY_NAME_KEY = "name"; + private const string MANIFEST_REGISTRY_URL_KEY = "url"; + private const string MANIFEST_REGISTRY_SCOPES_KEY = "scopes"; + + /// + /// Logger for this object. + /// + public Google.Logger Logger; + + /// + /// Parsed manifest data from the manifest JSON file. + /// + internal Dictionary manifestDict = null; + + /// + /// Construct an object to modify manifest file. + /// + public PackageManifestModifier() { Logger = new Google.Logger(); } + + /// + /// Construct an object based on another modifier. + /// + public PackageManifestModifier(PackageManifestModifier other) { + Logger = other.Logger; + try { + manifestDict = Json.Deserialize(other.GetManifestJson()) as Dictionary; + } catch (Exception e) { + Logger.Log(String.Format("Failed to clone PackageManifestModifier. \nException:{1}", + MANIFEST_FILE_PATH, e.ToString()), LogLevel.Error); + } + } + + /// + /// Read manifest from the file and parse JSON string into a dictionary. + /// + /// True if read and parsed successfully. + internal bool ReadManifest() { + string manifestText; + try { + manifestText = File.ReadAllText(MANIFEST_FILE_PATH); + } catch (Exception e) { + Logger.Log(String.Format("Failed to read {0}. \nException:{1}", + MANIFEST_FILE_PATH, e.ToString()), LogLevel.Error); + return false; + } + try { + manifestDict = Json.Deserialize(manifestText) as Dictionary; + } catch (Exception e) { + Logger.Log(String.Format("Failed to parse {0}. \nException:{1}", + MANIFEST_FILE_PATH, e.ToString()), LogLevel.Error); + return false; + } + if (manifestDict == null) { + Logger.Log(String.Format("Failed to read the {0} because it is empty or malformed.", + MANIFEST_FILE_PATH), LogLevel.Error); + return false; + } + return true; + } + + /// + /// Try to get the scopedRegistries entry from the manifest. + /// + /// List of scoped registries if found, null otherwise. + /// Throws ParseException if the manifest isn't loaded or there is an error parsing + /// the manifest. + private List ScopedRegistries { + get { + object scopedRegistriesObj = null; + if (manifestDict == null) { + throw new ParseException(String.Format("'{0}' not loaded.", MANIFEST_FILE_PATH)); + } else if (manifestDict.TryGetValue(MANIFEST_SCOPED_REGISTRIES_KEY, + out scopedRegistriesObj)) { + var scopedRegistries = scopedRegistriesObj as List; + if (scopedRegistries == null) { + throw new ParseException( + String.Format("Found malformed '{0}' section in '{1}'.", + MANIFEST_SCOPED_REGISTRIES_KEY, MANIFEST_FILE_PATH)); + } + return scopedRegistries; + } + return null; + } + } + + + /// + /// Extract scoped registries from the parsed manifest. + /// + internal Dictionary> PackageManagerRegistries { + get { + var upmRegistries = new Dictionary>(); + List scopedRegistries = null; + try { + scopedRegistries = ScopedRegistries; + } catch (ParseException exception) { + Logger.Log(exception.ToString(), level: LogLevel.Warning); + } + if (scopedRegistries == null) { + Logger.Log(String.Format("No registries found in '{0}'", MANIFEST_FILE_PATH), + level: LogLevel.Verbose); + return upmRegistries; + } + Logger.Log(String.Format("Reading '{0}' from '{1}'", + MANIFEST_SCOPED_REGISTRIES_KEY, MANIFEST_FILE_PATH), + level: LogLevel.Verbose); + foreach (var obj in scopedRegistries) { + var registry = obj as Dictionary; + if (registry == null) continue; + string name = null; + string url = null; + var scopes = new List(); + object nameObj = null; + object urlObj = null; + object scopesObj; + if (registry.TryGetValue(MANIFEST_REGISTRY_NAME_KEY, out nameObj)) { + name = nameObj as string; + } + if (registry.TryGetValue(MANIFEST_REGISTRY_URL_KEY, out urlObj)) { + url = urlObj as string; + } + if (registry.TryGetValue(MANIFEST_REGISTRY_SCOPES_KEY, + out scopesObj)) { + List scopesObjList = scopesObj as List; + if (scopesObjList != null && scopesObjList.Count > 0) { + foreach (var scopeObj in scopesObjList) { + string scope = scopeObj as string; + if (!String.IsNullOrEmpty(scope)) scopes.Add(scope); + } + } + } + var upmRegistry = new PackageManagerRegistry() { + Name = name, + Url = url, + Scopes = scopes, + CustomData = registry, + CreatedBy = MANIFEST_FILE_PATH + }; + if (!String.IsNullOrEmpty(name) && + !String.IsNullOrEmpty(url) && + scopes.Count > 0) { + List upmRegistryList; + if (!upmRegistries.TryGetValue(url, out upmRegistryList)) { + upmRegistryList = new List(); + upmRegistries[url] = upmRegistryList; + } + upmRegistryList.Add(upmRegistry); + Logger.Log(String.Format("Read '{0}' from '{1}'", + upmRegistry.ToString(), MANIFEST_FILE_PATH), + level: LogLevel.Verbose); + } else { + Logger.Log( + String.Format("Ignoring malformed registry {0} in {1}", + upmRegistry.ToString(), MANIFEST_FILE_PATH), + level: LogLevel.Warning); + } + + } + return upmRegistries; + } + } + + /// + /// Add a scoped registries. + /// + /// Registries to add to the manifest. + /// true if the registries are added to the manifest, false otherwise. + internal bool AddRegistries(IEnumerable registries) { + List scopedRegistries; + try { + scopedRegistries = ScopedRegistries; + } catch (ParseException exception) { + Logger.Log(String.Format("{0} Unable to add registries:\n", + exception.ToString(), + PackageManagerRegistry.ToString(registries)), + level: LogLevel.Error); + return false; + } + if (scopedRegistries == null) { + scopedRegistries = new List(); + manifestDict[MANIFEST_SCOPED_REGISTRIES_KEY] = scopedRegistries; + } + RemoveRegistries(registries, displayWarning: false); + foreach (var registry in registries) { + scopedRegistries.Add(new Dictionary() { + { MANIFEST_REGISTRY_NAME_KEY, registry.Name }, + { MANIFEST_REGISTRY_URL_KEY, registry.Url }, + { MANIFEST_REGISTRY_SCOPES_KEY, registry.Scopes } + }); + } + return true; + } + + /// + /// Remove all scoped registries in the given list. + /// + /// A list of scoped registry to be removed + /// Whether to display a warning if specified registries were not + /// found. + /// true if the registries could be removed, false otherwise. + internal bool RemoveRegistries(IEnumerable registries, + bool displayWarning = true) { + List scopedRegistries = null; + try { + scopedRegistries = ScopedRegistries; + } catch (ParseException exception) { + Logger.Log(String.Format("{0} Unable to remove registries:\n", exception.ToString(), + PackageManagerRegistry.ToString(registries)), + level: LogLevel.Error); + return false; + } + int removed = 0; + int numberOfRegistries = 0; + var scopedRegistriesByUrl = PackageManagerRegistries; + foreach (var registry in registries) { + numberOfRegistries ++; + List existingRegistries; + if (scopedRegistriesByUrl.TryGetValue(registry.Url, out existingRegistries)) { + int remaining = existingRegistries.Count; + foreach (var existingRegistry in existingRegistries) { + if (scopedRegistries.Remove(existingRegistry.CustomData)) { + remaining --; + } else { + Logger.Log(String.Format("Failed to remove registry '{0}' from '{1}'", + existingRegistry, MANIFEST_FILE_PATH), + level: LogLevel.Error); + } + } + if (remaining == 0) removed ++; + } + } + if (displayWarning) { + Logger.Log(String.Format("Removed {0}/{1} registries from '{2}'", + removed, numberOfRegistries, MANIFEST_FILE_PATH), + level: removed == numberOfRegistries ? LogLevel.Verbose : LogLevel.Warning); + } + return removed == numberOfRegistries; + } + + /// + /// Write the dictionary to manifest file. + /// + /// True if serialized and wrote successfully. + internal bool WriteManifest() { + if (manifestDict == null) { + Logger.Log(String.Format("No manifest to write to '{0}'", MANIFEST_FILE_PATH), + level: LogLevel.Error); + return false; + } + try { + string manifestText = GetManifestJson(); + + if (!String.IsNullOrEmpty(manifestText)) { + File.WriteAllText(MANIFEST_FILE_PATH, manifestText); + } + } catch (Exception e) { + Logger.Log( + String.Format("Failed to write to {0}. \nException:{1}", + MANIFEST_FILE_PATH, e.ToString()), + level: LogLevel.Error); + return false; + } + return true; + } + + /// + /// Get the manifest json string. + /// + /// Manifest json string. + internal string GetManifestJson() { + return manifestDict != null ? + Json.Serialize(manifestDict, humanReadable: true, indentSpaces: 2) : + ""; + } +} +} // namespace Google diff --git a/source/PackageManagerResolver/src/PackageMigrator.cs b/source/PackageManagerResolver/src/PackageMigrator.cs new file mode 100644 index 00000000..96f8ce6f --- /dev/null +++ b/source/PackageManagerResolver/src/PackageMigrator.cs @@ -0,0 +1,1068 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using UnityEditor; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using System.Xml; + +namespace Google { + +/// +/// Searches for packages installed in the project that are available in UPM and prompts the +/// developer to migrate them to UPM. +/// +[InitializeOnLoad] +internal class PackageMigrator { + /// + /// A unique class to create the multi-select window to migrate packages. + /// + private class PackageMigratorWindow : MultiSelectWindow {} + + /// + /// Map of Version Handler determined package to Package Manager Package ID and version. + /// + internal class PackageMap { + + /// + /// Available Package Manager package information indexed by package ID. + /// This should be set via CacheAvailablePackageInfo() before creating new (i.e + /// not read from a file) PackageMap instances. + private static readonly Dictionary + availablePackageInfoCache = + new Dictionary(); + + /// + /// Installed Package Manager package information indexed by package ID. + /// This should be set via CacheInstalledPackageInfo() before creating PackageMap + /// instances. + private static readonly Dictionary + installedPackageInfoCache = + new Dictionary(); + + /// + /// Version Handler package name. + /// + public string VersionHandlerPackageName { get; set; } + + /// + /// Version Handler package name and version string. + /// + public string VersionHandlerPackageId { + get { + return String.Format("'{0}' v{1}", VersionHandlerPackageName, + VersionHandlerPackageVersion); + } + } + + /// + /// Get the files associated with the version handler package. + /// + public VersionHandlerImpl.ManifestReferences VersionHandlerManifest { + get { + if (!String.IsNullOrEmpty(VersionHandlerPackageName)) { + var manifestsByPackageName = + VersionHandlerImpl.ManifestReferences. + FindAndReadManifestsInAssetsFolderByPackageName(); + VersionHandlerImpl.ManifestReferences manifestReferences; + if (manifestsByPackageName.TryGetValue(VersionHandlerPackageName, + out manifestReferences)) { + return manifestReferences; + } + } + return null; + } + } + + /// + /// Get the version string from the Version Handler package. + /// + public string VersionHandlerPackageVersion { + get { + var manifest = VersionHandlerManifest; + return manifest != null ? manifest.currentMetadata.versionString : null; + } + } + + /// + /// Get the numeric version from the Version Handler package. + /// + public long VersionHandlerPackageCalculatedVersion { + get { + var manifest = VersionHandlerManifest; + return manifest != null ? manifest.currentMetadata.CalculateVersion() : 0; + } + } + + /// + /// Get the Package Manager Package ID in the form "name@version". + /// + public string PackageManagerPackageId { get; set; } + + /// + /// Get the Package Manager package info associated with the specific package ID. + /// + public PackageManagerClient.PackageInfo AvailablePackageManagerPackageInfo { + get { + return FindPackageInfoById(availablePackageInfoCache, PackageManagerPackageId); + } + } + + /// + /// Get the Package Manager package info associated with the specific package ID. + /// + public PackageManagerClient.PackageInfo InstalledPackageManagerPackageInfo { + get { + return FindPackageInfoById(installedPackageInfoCache, PackageManagerPackageId); + } + } + + /// + /// Determine whether a package has been migrated. + /// + public bool Migrated { + get { + return InstalledPackageManagerPackageInfo != null && + VersionHandlerManifest == null; + } + } + + /// + /// Construct an empty package map. + /// + public PackageMap() { + VersionHandlerPackageName = ""; + PackageManagerPackageId = ""; + } + + /// + /// Compare with this object. + /// + /// Object to compare with. + /// true if both objects have the same contents excluding CreatedBy, + /// false otherwise. + public override bool Equals(System.Object obj) { + var other = obj as PackageMap; + return other != null && + VersionHandlerPackageName == other.VersionHandlerPackageName && + PackageManagerPackageId == other.PackageManagerPackageId; + } + + /// + /// Geneerate a hash of this object. + /// + /// Hash of this object. + public override int GetHashCode() { + return VersionHandlerPackageName.GetHashCode() ^ + PackageManagerPackageId.GetHashCode(); + } + + /// + /// Convert to a human readable string. + /// + public override string ToString() { + return String.Format("Migrated: {0}, {1} -> '{2}'", Migrated, VersionHandlerPackageId, + PackageManagerPackageId); + } + + /// + /// Stores a set of PackageMap instances. + /// + internal static readonly string PackageMapFile = + Path.Combine("Temp", "PackageManagerResolverPackageMap.xml"); + + /// + /// Read a set of PackageMap instances from a file. + /// + /// List of package maps. + /// Thrown if an error occurs while reading the + /// file. + public static ICollection ReadFromFile() { + var packageMaps = new List(); + if (!File.Exists(PackageMapFile)) return packageMaps; + + PackageMap currentPackageMap = null; + if (!XmlUtilities.ParseXmlTextFileElements( + PackageMapFile, PackageMigrator.Logger, + (reader, elementName, isStart, parentElementName, elementNameStack) => { + if (elementName == "packageMaps" && parentElementName == "") { + if (isStart) { + currentPackageMap = new PackageMap(); + } else { + if (currentPackageMap != null && + !String.IsNullOrEmpty( + currentPackageMap.VersionHandlerPackageName) && + !String.IsNullOrEmpty( + currentPackageMap.PackageManagerPackageId)) { + packageMaps.Add(currentPackageMap); + } + } + return true; + } else if (elementName == "packageMap" && parentElementName == "packageMaps") { + return true; + } else if (elementName == "versionHandlerPackageName" && + parentElementName == "packageMap") { + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + currentPackageMap.VersionHandlerPackageName = + reader.ReadContentAsString(); + } + return true; + } else if (elementName == "unityPackageManagerPackageId" && + parentElementName == "packageMap") { + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + currentPackageMap.PackageManagerPackageId = + reader.ReadContentAsString(); + } + return true; + } + PackageMigrator.Logger.Log( + String.Format("{0}:{1}: Unknown XML element '{2}', parent '{3}'", + PackageMapFile, reader.LineNumber, elementName, + parentElementName, reader.LineNumber), + level: LogLevel.Error); + return false; + })) { + throw new IOException(String.Format("Failed to read package map file {0}. " + + "Package migration will likely fail.", + PackageMapFile)); + } + return packageMaps; + } + + /// + /// Generate a collection sorted by Version Handler package name. + /// + /// Sorted list of package maps. + private static ICollection SortedPackageMap( + IEnumerable packages) { + var sorted = new SortedDictionary(); + foreach (var pkg in packages) { + if (!String.IsNullOrEmpty(pkg.VersionHandlerPackageName)) { + sorted[pkg.VersionHandlerPackageName] = pkg; + } + } + return sorted.Values; + } + + /// + /// Write a set of PackageMap instances to a file. + /// + /// Package maps to write to a file. + /// Thrown if an error occurs while writing the + /// file. + public static void WriteToFile(ICollection packageMaps) { + try { + if (packageMaps.Count == 0) { + var failed = FileUtils.DeleteExistingFileOrDirectory(PackageMapFile); + if (failed.Count == 0) { + return; + } + throw new IOException(String.Format( + "Failed to delete {0}, package migration may attempt to resume from the " + + "cached state.", PackageMapFile)); + } + Directory.CreateDirectory(Path.GetDirectoryName(PackageMapFile)); + using (var writer = new XmlTextWriter(new StreamWriter(PackageMapFile)) { + Formatting = Formatting.Indented, + }) { + writer.WriteStartElement("packageMaps"); + foreach (var pkg in SortedPackageMap(packageMaps)) { + writer.WriteStartElement("packageMap"); + writer.WriteStartElement("versionHandlerPackageName"); + writer.WriteValue(pkg.VersionHandlerPackageName); + writer.WriteEndElement(); + writer.WriteStartElement("unityPackageManagerPackageId"); + writer.WriteValue(pkg.PackageManagerPackageId); + writer.WriteEndElement(); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + } catch (Exception e) { + throw new IOException( + String.Format("Failed to write package map file {0} ({1})\n" + + "Package migration will likely fail.", PackageMapFile, e), e); + } + } + + /// + /// Lookup a package in the info cache. + /// + /// Cache to search. + /// Package ID to search with. + /// PackageInfo if found, null otherwise. + private static PackageManagerClient.PackageInfo FindPackageInfoById( + Dictionary cache, string packageId) { + PackageManagerClient.PackageInfo packageInfo; + if (cache.TryGetValue(packageId, out packageInfo)) { + return packageInfo; + } + return null; + } + + /// + /// Delegate used to list / search for Package Manager packages. + /// + /// Called with the result of the list / search operation. + private delegate void SearchPackageManagerDelegate( + Action result); + + /// + /// Reports progress of the package search process. + /// + /// Progress (0..1). + /// Description of the operation being performed. + public delegate void FindPackagesProgressDelegate(float progress, string description); + + /// + /// Cache Package Manager packages. + /// + /// Whether to refresh the cache or data in memory. + /// Search operation that returns a result to cache. + /// Type of search operation being performed for error + /// reporting. + /// Cache of PackageInfo objects to update. + /// Called when packages have been cached by this class. + /// Called as the operation progresses. + /// Length of time to use to simulate + /// progress of the search operation. The search will be reported as complete in this + /// length of time. + private static void CachePackageInfo( + bool refresh, SearchPackageManagerDelegate search, string searchDescription, + Dictionary cache, + Action complete, + FindPackagesProgressDelegate progressDelegate, + float simulateProgressTimeInMilliseconds) { + if (!refresh && cache.Count > 0) { + progressDelegate(1.0f, searchDescription); + complete(new PackageManagerClient.Error(null)); + return; + } + progressDelegate(0.0f, searchDescription); + + // Start a job to report progress during the search operation. + const float ProgressReportIntervalInMilliseconds = 1000.0f; + var progressPerUpdate = ProgressReportIntervalInMilliseconds / + simulateProgressTimeInMilliseconds; + object progressState = 0.0f; + var progressJob = new RunOnMainThread.PeriodicJob(() => { + float progress = (float)progressState; + progressDelegate(progress, searchDescription); + progressState = Math.Min(progress + progressPerUpdate, 1.0f); + return false; + }) { + IntervalInMilliseconds = ProgressReportIntervalInMilliseconds + }; + progressJob.Execute(); + + search((result) => { + var finishedDescription = searchDescription; + if (!String.IsNullOrEmpty(result.Error.Message)) { + finishedDescription = String.Format("{0} failed ({1})", searchDescription, + result.Error); + Logger.Log(finishedDescription, level: LogLevel.Error); + } else { + cache.Clear(); + foreach (var pkg in result.Packages) cache[pkg.PackageId] = pkg; + } + progressJob.Stop(); + progressDelegate(1.0f, finishedDescription); + complete(result.Error); + }); + } + + /// + /// Cache installed Package Manager packages. + /// + /// Whether to refresh the cache or data in memory. + /// Called when packages have been cached by this class. + /// Called as the operation progresses. + public static void CacheInstalledPackageInfo( + bool refresh, Action complete, + FindPackagesProgressDelegate progressDelegate) { + CachePackageInfo(refresh, PackageManagerClient.ListInstalledPackages, + "Listing installed UPM packages", + installedPackageInfoCache, complete, progressDelegate, + 2000.0f /* 2 seconds */); + } + + /// + /// Cache available Package Manager packages. + /// + /// Whether to refresh the cache or data in memory. + /// Called when packages have been cached by this class. + /// Called as the operation progresses. + public static void CacheAvailablePackageInfo( + bool refresh, Action complete, + FindPackagesProgressDelegate progressDelegate) { + CachePackageInfo(refresh, PackageManagerClient.SearchAvailablePackages, + "Searching for available UPM packages", + availablePackageInfoCache, complete, progressDelegate, + 20000.0f /* 20 seconds */); + } + + /// + /// Cache available and installed Package Manager packages. + /// + /// Whether to refresh the cache or data in memory. + /// Called when packages have been cached by this class. + /// Called as the operation progresses. + public static void CachePackageInfo(bool refresh, + Action complete, + FindPackagesProgressDelegate progressDelegate) { + // Fraction of caching progress for each of the following operations. + const float CacheInstallPackageProgress = 0.1f; + const float CacheAvailablePackageProgress = 0.9f; + + CacheInstalledPackageInfo(refresh, (installedError) => { + if (!String.IsNullOrEmpty(installedError.Message)) { + complete(installedError); + return; + } + CacheAvailablePackageInfo(refresh, (availableError) => { + complete(availableError); + }, + (progress, description) => { + progressDelegate(CacheInstallPackageProgress + + (progress * CacheAvailablePackageProgress), + description); + }); + }, + (progress, description) => { + progressDelegate(progress * CacheInstallPackageProgress, + description); + }); + } + + /// + /// Find packages in the project managed by the Version Handler that can be migrated to + /// the Package Manager. + /// + /// Called with an error string (empty if no error occured) and the + /// list of packages that should be migrated. + /// Called as the operation progresses. + /// Include UPM packages that are older than the + /// packages installed in the project. + public static void FindPackagesToMigrate(Action> complete, + FindPackagesProgressDelegate progressDelegate, + bool includeOutOfDatePackages = false) { + if (!PackageMigrator.Available) { + complete(PackageMigrator.NotAvailableMessage, new PackageMap[] {}); + return; + } + var packageMaps = new HashSet(); + + progressDelegate(0.0f, "Searching for managed Version Handler packages"); + var installedManifestsByPackageName = VersionHandlerImpl.ManifestReferences. + FindAndReadManifestsInAssetsFolderByPackageName(); + var installedManifestsByNamesAndAliases = + new Dictionary( + installedManifestsByPackageName); + foreach (var manifest in installedManifestsByPackageName.Values) { + foreach (var manifestAlias in manifest.Aliases) { + installedManifestsByNamesAndAliases[manifestAlias] = manifest; + } + } + var installedManifestPackageNames = + new HashSet(installedManifestsByNamesAndAliases.Keys); + + // If no Version Handler packages are installed, return an empty set. + if (installedManifestsByNamesAndAliases.Count == 0) { + progressDelegate(1.0f, "No Version Handler packages found"); + complete("", packageMaps); + return; + } + + Logger.Log(String.Format( + "Detected version handler packages:\n{0}", + String.Join("\n", (new List( + installedManifestsByPackageName.Keys)).ToArray())), + level: LogLevel.Verbose); + + CachePackageInfo(true, (error) => { + if (!String.IsNullOrEmpty(error.Message)) { + progressDelegate(1.0f, String.Format("Failed: {0}", error.Message)); + complete(error.Message, packageMaps); + return; + } + var packageInfos = new List( + installedPackageInfoCache.Values); + packageInfos.AddRange(availablePackageInfoCache.Values); + + foreach (var pkg in packageInfos) { + var foundVersionHandlerPackageNames = pkg.GetVersionHandlerPackageNames(); + var versionHandlerPackageNames = + new HashSet(foundVersionHandlerPackageNames); + versionHandlerPackageNames.IntersectWith(installedManifestPackageNames); + + if (versionHandlerPackageNames.Count == 0) { + Logger.Log(String.Format( + "{0} does not map to an installed Version Handler package [{1}]", + pkg.PackageId, + String.Join(", ", (new List( + foundVersionHandlerPackageNames)).ToArray())), + level: LogLevel.Debug); + continue; + } + + // Map all aliases to the canonical names of version handler packages to + // migrate. + var canonicalVersionHandlerPackageNames = new HashSet(); + var appliedMappings = new List(); + foreach (var versionHandlerPackageName in versionHandlerPackageNames) { + var canonicalName = installedManifestsByNamesAndAliases[ + versionHandlerPackageName].filenameCanonical; + canonicalVersionHandlerPackageNames.Add(canonicalName); + if (canonicalName == versionHandlerPackageName || + versionHandlerPackageNames.Contains(canonicalName)) { + continue; + } + appliedMappings.Add(String.Format( + "'{0}' UPM package references VH package '{1}' with canonical " + + "name '{2}'", pkg.PackageId, versionHandlerPackageName, + canonicalName)); + } + if (appliedMappings.Count > 0) { + Logger.Log(String.Format("Mapped VH package aliases:\n{0}", + String.Join("\n", appliedMappings.ToArray())), + level: LogLevel.Verbose); + } + + foreach (var versionHandlerPackageName in + canonicalVersionHandlerPackageNames) { + var packageMap = new PackageMap() { + VersionHandlerPackageName = versionHandlerPackageName, + PackageManagerPackageId = pkg.PackageId + }; + Logger.Log( + String.Format("Found Version Handler package to migrate to UPM " + + "package {0} --> '{1}'.", + packageMap.VersionHandlerPackageId, + packageMap.PackageManagerPackageId), + level: LogLevel.Verbose); + if (!includeOutOfDatePackages && + packageMap.AvailablePackageManagerPackageInfo. + CalculateVersion() < + packageMap.VersionHandlerPackageCalculatedVersion) { + Logger.Log( + String.Format( + "Ignoring UPM package '{0}' as it's older than " + + "installed package '{1}' at version {2}.", + packageMap.PackageManagerPackageId, + packageMap.VersionHandlerPackageName, + packageMap.VersionHandlerPackageVersion), + level: LogLevel.Verbose); + continue; + } + packageMaps.Add(packageMap); + } + } + + complete("", packageMaps); + }, progressDelegate); + } + + /// + /// Calculate migration progress through the specified list. + /// + /// List of packages to migrated. + /// A value between 0 (no progress) and 1 (complete). + public static float CalculateProgress(ICollection packages) { + int total = packages.Count; + int migrated = 0; + foreach (var pkg in packages) { + if (pkg.Migrated) migrated ++; + } + return total > 0 ? (float)migrated / (float)total : 1.0f; + } + } + + /// + /// Logger for this module. + /// + public static Logger Logger = PackageManagerResolver.logger; + + /// + /// Job queue to execute package migration. + /// + private static RunOnMainThread.JobQueue migrationJobQueue = new RunOnMainThread.JobQueue(); + + /// + /// Packages being migrated. + /// + private static ICollection inProgressPackageMaps = null; + + /// + /// Enumerates through set of packages being migrated. + /// + private static IEnumerator inProgressPackageMapsEnumerator = null; + + /// + /// Reports progress of the package migration process. + /// + /// Progress (0..1). + /// Package being migrated or null if complete. + private delegate void ProgressDelegate(float progress, PackageMap packageMap); + + /// + /// Determine whether the package manager with scoped registries is available. + /// + public static bool Available { + get { + return PackageManagerClient.Available && + PackageManagerResolver.ScopedRegistriesSupported; + } + } + + /// + /// Message displayed if package migration doesn't work. + /// + private const string NotAvailableMessage = + "Unity Package Manager with support for scoped registries is not " + + "available in this version of Unity."; + + /// + /// Read the package migration state. + /// + /// true if the migration state is already in memory or read successfully and contains + /// a non-zero number of PackageMap instances, false otherwise. + /// Throws IOException if the read fails. + private static bool ReadMigrationState() { + if (inProgressPackageMaps == null || inProgressPackageMapsEnumerator == null) { + // Read in-progress migrations including progress through the queue. + try { + inProgressPackageMaps = PackageMap.ReadFromFile(); + inProgressPackageMapsEnumerator = inProgressPackageMaps.GetEnumerator(); + } catch (IOException error) { + ClearMigrationState(); + throw error; + } + } else { + inProgressPackageMapsEnumerator.Reset(); + } + return inProgressPackageMapsEnumerator.MoveNext(); + } + + + /// + /// Clear the package migration state. + /// + private static void ClearMigrationState() { + inProgressPackageMaps = null; + inProgressPackageMapsEnumerator = null; + PackageMap.WriteToFile(new PackageMap[] {}); + } + + /// + /// Migrate the next package in the queue. + /// + /// + /// This method assumes it's being executed as a job on migrationJobQueue so + /// migrationJobQueue.Complete() must be called before the complete() method in all code paths. + /// + /// When complete, called with a null string if successful, + /// called with an error message otherwise. + /// Called to report progress. + private static void MigrateNext(Action complete, ProgressDelegate progress) { + do { + var packageMap = inProgressPackageMapsEnumerator.Current; + Logger.Log(String.Format("Examining package to migrate: {0}", packageMap.ToString()), + level: LogLevel.Verbose); + if (!packageMap.Migrated) { + progress(PackageMap.CalculateProgress(inProgressPackageMaps), packageMap); + + Logger.Log(String.Format("Removing {0} to replace with {1}", + packageMap.VersionHandlerPackageName, + packageMap.PackageManagerPackageId), + level: LogLevel.Verbose); + // Uninstall the .unitypackage. + var deleteResult = VersionHandlerImpl.ManifestReferences.DeletePackages( + new HashSet() { packageMap.VersionHandlerPackageName }, + force: true); + if (deleteResult.Count > 0) { + var error = String.Format("Uninstallation of .unitypackage '{0}' failed, " + + "halted package migration to '{1}'. You will need " + + "to reinstall '{0}' to restore your project", + packageMap.VersionHandlerPackageName, + packageMap.PackageManagerPackageId); + migrationJobQueue.Complete(); + complete(error); + return; + } + + Logger.Log(String.Format("Installing {0} to replace {1}", + packageMap.PackageManagerPackageId, + packageMap.VersionHandlerPackageName), + level: LogLevel.Verbose); + PackageManagerClient.AddPackage( + packageMap.PackageManagerPackageId, + (result) => { + if (!String.IsNullOrEmpty(result.Error.Message)) { + var error = String.Format("Installation of package '{0}' failed, " + + "halted package migration ({1}). You will " + + "need to reinstall {2} to restore your " + + "project.", + packageMap.PackageManagerPackageId, + result.Error.Message, + packageMap.VersionHandlerPackageName); + migrationJobQueue.Complete(); + complete(error); + } else { + MigrateNext(complete, progress); + } + }); + return; + } + } while (inProgressPackageMapsEnumerator.MoveNext()); + + Logger.Log("Package migration complete", level: LogLevel.Verbose); + progress(1.0f, null); + ClearMigrationState(); + migrationJobQueue.Complete(); + complete(null); + } + + /// + /// Start / resume package migration. + /// + /// When complete, called with a null string if successful, + /// called with an error message otherwise. + /// Called to report progress of package + /// migration. + /// Called to report progress of package + /// migration initialization. + private static void StartOrResumeMigration( + Action complete, ProgressDelegate migratePackagesProgressDelegate, + PackageMap.FindPackagesProgressDelegate findPackagesProgressDelegate) { + migrationJobQueue.Schedule(() => { + // Read in-progress migrations including progress through the queue. + try { + if (!ReadMigrationState()) { + migrationJobQueue.Complete(); + complete(null); + return; + } + } catch (IOException ioError) { + PackageManagerResolver.analytics.Report( + "package_migrator/migration/failed/read_snapshot", + "Migrate Packages: Read Snapshot Failed"); + migrationJobQueue.Complete(); + complete(ioError.Message); + return; + } + // Fetch the list of installed packages before starting migration. + PackageMap.CacheInstalledPackageInfo( + false, (error) => { + if (!String.IsNullOrEmpty(error.Message)) { + PackageManagerResolver.analytics.Report( + "package_migrator/migration/failed/find_packages", + "Migrate Packages: Find Packages Failed"); + migrationJobQueue.Complete(); + complete(error.Message); + return; + } + // Migrate packages. + MigrateNext(complete, migratePackagesProgressDelegate); + }, findPackagesProgressDelegate); + }); + } + + /// + /// Called when the assembly is initialized by Unity. + /// + static PackageMigrator() { + ResumeMigration(); + } + + /// + /// Report that package migration failed. + /// + private static void ReportPackageMigrationFailed() { + int numberOfSelectedPackages = -1; + int numberOfMigratedPackages = -1; + try { + ReadMigrationState(); + numberOfSelectedPackages = inProgressPackageMaps.Count; + numberOfMigratedPackages = 0; + foreach (var packageMap in inProgressPackageMaps) { + if (packageMap.Migrated) numberOfMigratedPackages ++; + } + } catch (IOException) { + // Ignore the exception. + } + PackageManagerResolver.analytics.Report( + "package_migrator/migration/failed", + new KeyValuePair[] { + new KeyValuePair("selected", numberOfSelectedPackages.ToString()), + new KeyValuePair("migrated", numberOfMigratedPackages.ToString()), + }, + "Migrate Packages: Failed"); + } + + + /// + /// Resume migration after an app domain reload. + /// + public static void ResumeMigration() { + RunOnMainThread.Run(() => { + if (!Available) return; + try { + if (ReadMigrationState()) { + Logger.Log(String.Format("Resuming migration from {0} of {1} packages", + PackageMap.PackageMapFile, + inProgressPackageMaps.Count), + level: LogLevel.Verbose); + TryMigration((error) => { + if (!String.IsNullOrEmpty(error)) { + Logger.Log(String.Format("Migration failed: {0}", error), + level: LogLevel.Error); + } + }); + } + } catch (IOException ioError) { + Logger.Log(String.Format("Failed to resume package migration: {0}", ioError), + level: LogLevel.Error); + ReportPackageMigrationFailed(); + } + }, runNow: false); + } + + /// + /// Displayed when searching for / migrating packages. + /// + const string WindowTitle = "Migrating Packages"; + + /// + /// Displays progress of a find packages operation. + /// + /// Progress (0..1). + /// Description of the operation being performed. + private static void UpdateFindProgressBar(float progress, string description) { + var message = String.Format("Finding package(s) to migrate {0}%: {1}", + (int)(progress * 100.0f), description); + Logger.Log(message, level: LogLevel.Verbose); + EditorUtility.DisplayProgressBar(WindowTitle, description, progress); + } + + /// + /// Display progress of a package migration operation. + /// + /// Progress (0..1). + /// Package being migrated. + private static void UpdatePackageMigrationProgressBar(float progress, PackageMap packageMap) { + var description = packageMap != null ? + String.Format("{0} --> {1}", + packageMap.VersionHandlerPackageId, + packageMap.PackageManagerPackageId) : "(none)"; + var message = String.Format("Migrating package(s) {0}%: {1}", + (int)(progress * 100.0f), description); + Logger.Log(message, level: LogLevel.Verbose); + EditorUtility.DisplayProgressBar(WindowTitle, description, progress); + } + + /// + /// Run through the whole process. + /// + /// Called when migration is complete with an error message if + /// it fails. + public static void TryMigration(Action complete) { + if (!Available) { + complete(NotAvailableMessage); + return; + } + + Action clearProgressAndComplete = (error) => { + EditorUtility.ClearProgressBar(); + if (String.IsNullOrEmpty(error)) { + PackageManagerResolver.analytics.Report( + "package_migrator/migration/success", + new KeyValuePair[] { + new KeyValuePair( + "migrated", inProgressPackageMaps.Count.ToString()), + }, + "Migrate Packages: Success"); + } + ClearMigrationState(); + complete(error); + }; + + StartOrResumeMigration((migrationError) => { + if (!String.IsNullOrEmpty(migrationError)) { + clearProgressAndComplete(migrationError); + return; + } + + PackageMap.FindPackagesToMigrate((error, packageMaps) => { + if (!String.IsNullOrEmpty(error)) { + PackageManagerResolver.analytics.Report( + "package_migrator/migration/failed/find_packages", + "Migrate Packages: Find Packages Failed"); + clearProgressAndComplete(error); + return; + } + try { + PackageMap.WriteToFile(packageMaps); + } catch (IOException e) { + EditorUtility.ClearProgressBar(); + clearProgressAndComplete(e.Message); + return; + } + StartOrResumeMigration(clearProgressAndComplete, + UpdatePackageMigrationProgressBar, + UpdateFindProgressBar); + }, UpdateFindProgressBar); + }, UpdatePackageMigrationProgressBar, UpdateFindProgressBar); + } + + /// + /// Display a window to select which packages to install. + /// + /// Set of package maps available for selection. + /// Called with a set of items selected from the specified + /// list. + public static void DisplaySelectionWindow(ICollection packageMaps, + Action> selectedPackageMaps) { + var packageMapsByPackageId = new Dictionary(); + var items = new List>(); + foreach (var pkg in packageMaps) { + packageMapsByPackageId[pkg.PackageManagerPackageId] = pkg; + items.Add(new KeyValuePair( + pkg.PackageManagerPackageId, + String.Format("{0} --> {1}", pkg.VersionHandlerPackageId, + pkg.PackageManagerPackageId))); + } + + var window = MultiSelectWindow.CreateMultiSelectWindow(WindowTitle); + window.minSize = new UnityEngine.Vector2(800, 400); + window.SelectedItems = new HashSet(packageMapsByPackageId.Keys); + window.AvailableItems = items; + window.Sort(1); + window.Caption = + "Select the set of packages to migrate from being installed under " + + "the Assets folder to being installed using the Unity Package Manager\n\n" + + "As each package is migrated, it will be removed from the Assets folder and " + + "installed via the Unity Package Manager. If this package (EDM4U) is being " + + "migrated the progress bar will disappear as it is migrated and migration will " + + "resume when this package is reloaded after being installed by the Unity " + + "Package Manager"; + window.OnApply = () => { + var selected = new List(); + foreach (var item in window.SelectedItems) { + selected.Add(packageMapsByPackageId[item]); + } + selectedPackageMaps(selected); + }; + window.OnCancel = () => { + selectedPackageMaps(new PackageMap[] {}); + }; + } + + /// + /// Display an error in a dialog. + /// + /// Error message to display. + private static void DisplayError(string error) { + Logger.Log(error, level: LogLevel.Error); + DialogWindow.Display(WindowTitle, error, DialogWindow.Option.Selected0, "OK"); + } + + /// + /// Find packages to migrate, display a window to provide the user a way to select the packages + /// to migrate and start migration if they're selected. + /// + [MenuItem("Assets/External Dependency Manager/Package Manager Resolver/Migrate Packages")] + public static void MigratePackages() { + PackageMap.FindPackagesToMigrate((findError, availablePackageMaps) => { + EditorUtility.ClearProgressBar(); + + // If an error occurs, display a dialog. + if (!String.IsNullOrEmpty(findError)) { + PackageManagerResolver.analytics.Report( + "package_migrator/migration/failed/find_packages", + "Migrate Packages: Find Packages Failed"); + DisplayError(findError); + return; + } + + // Show a package selection window and start migration if the user selects apply. + DisplaySelectionWindow(availablePackageMaps, (selectedPackageMaps) => { + if (selectedPackageMaps.Count == 0) { + PackageManagerResolver.analytics.Report( + "package_migrator/migration/canceled", + "Migrate Packages: Canceled"); + ClearMigrationState(); + return; + } + try { + ClearMigrationState(); + PackageMap.WriteToFile(selectedPackageMaps); + } catch (IOException e) { + DisplayError(String.Format("Migration failed ({0})", e.Message)); + PackageManagerResolver.analytics.Report( + "package_migrator/migration/failed/write_snapshot", + "Migrate Packages: Write Snapshot Failed"); + return; + } + + TryMigration((migrationError) => { + if (!String.IsNullOrEmpty(migrationError)) { + DisplayError(migrationError); + } + }); + }); + }, UpdateFindProgressBar); + } +} + +/// +/// Extension class to retrieve Version Handler package information from PackageInfo. +/// +internal static class PackageInfoVersionHandlerExtensions { + + /// + /// Regular expression that extracts the Version Handler package name from a label. + /// + private static Regex KEYWORD_VERSION_HANDLER_NAME_REGEX = new Regex("^vh[-_]name:(.*)"); + + /// + /// Get Version Handler package names associated with this Package Manager package. + /// + /// Set of package names associataed with this package. + public static HashSet GetVersionHandlerPackageNames( + this PackageManagerClient.PackageInfo packageInfo) { + var versionHandlerPackageNames = new HashSet(); + foreach (var keyword in packageInfo.Keywords) { + var match = KEYWORD_VERSION_HANDLER_NAME_REGEX.Match(keyword); + if (match.Success) versionHandlerPackageNames.Add(match.Groups[1].Value); + } + return versionHandlerPackageNames; + } + + /// + /// Get a numeric version of the Package Manager package. + /// + /// Numeric version of the package that can be compared with Version Handler package + /// versions. + public static long CalculateVersion(this PackageManagerClient.PackageInfo packageInfo) { + var version = packageInfo.Version; + return version != null ? + VersionHandlerImpl.FileMetadata.CalculateVersion(version) : 0; + } +} + +} diff --git a/source/PackageManagerResolver/src/SettingsDialog.cs b/source/PackageManagerResolver/src/SettingsDialog.cs new file mode 100644 index 00000000..f323edc6 --- /dev/null +++ b/source/PackageManagerResolver/src/SettingsDialog.cs @@ -0,0 +1,234 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Google { + +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +/// +/// Settings dialog for PackageManagerResolver. +/// +public class PackageManagerResolverSettingsDialog : EditorWindow +{ + /// + /// Loads / saves settings for this dialog. + /// + private class Settings { + /// + /// Whether enable external registries to be added to this Unity project + /// + internal bool enable; + + /// + /// Whether to prompt the user before to adding external registries. + /// + internal bool promptToAddRegistries; + + + /// + /// Whether to prompt the user to migrate Version Handler to UPM packages after a + /// registry has been added. + /// + internal bool promptToMigratePackages; + + /// + /// Whether to enable / disable verbose logging. + /// + internal bool verboseLoggingEnabled; + + /// + /// Whether settings are project specific. + /// + internal bool useProjectSettings; + + /// + /// Analytics settings. + /// + internal EditorMeasurement.Settings analyticsSettings; + + /// + /// Load settings into the dialog. + /// + internal Settings() { + enable = PackageManagerResolver.Enable; + promptToAddRegistries = PackageManagerResolver.PromptToAddRegistries; + promptToMigratePackages = PackageManagerResolver.PromptToMigratePackages; + verboseLoggingEnabled = PackageManagerResolver.VerboseLoggingEnabled; + useProjectSettings = PackageManagerResolver.UseProjectSettings; + analyticsSettings = + new EditorMeasurement.Settings(PackageManagerResolver.analytics); + } + + /// + /// Save dialog settings to preferences. + /// + internal void Save() { + PackageManagerResolver.Enable = enable; + PackageManagerResolver.PromptToAddRegistries = promptToAddRegistries; + PackageManagerResolver.PromptToMigratePackages = promptToMigratePackages; + PackageManagerResolver.VerboseLoggingEnabled = verboseLoggingEnabled; + PackageManagerResolver.UseProjectSettings = useProjectSettings; + analyticsSettings.Save(); + } + } + + private Settings settings; + + private Vector2 scrollPosition = new Vector2(0, 0); + + /// + /// Load settings for this dialog. + /// + private void LoadSettings() { + settings = new Settings(); + } + + /// + /// Setup the window's initial position and size. + /// + public void Initialize() { + minSize = new Vector2(350, 430); + position = new Rect(UnityEngine.Screen.width / 3, + UnityEngine.Screen.height / 3, + minSize.x, minSize.y); + PackageManagerResolver.analytics.Report("settings/show", "Settings"); + } + + /// + /// Called when the window is loaded. + /// + public void OnEnable() { + LoadSettings(); + } + + /// + /// Called when the GUI should be rendered. + /// + public void OnGUI() { + GUI.skin.label.wordWrap = true; + + GUILayout.BeginVertical(); + + GUILayout.Label(String.Format("{0} (version {1}.{2}.{3})", + PackageManagerResolver.PLUGIN_NAME, + PackageManagerResolverVersionNumber.Value.Major, + PackageManagerResolverVersionNumber.Value.Minor, + PackageManagerResolverVersionNumber.Value.Build)); + + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + + if (!PackageManagerResolver.ScopedRegistriesSupported) { + GUILayout.Label( + String.Format("Only supported from Unity {0} and above.", + PackageManagerResolver.MinimumUnityVersionString)); + } + + // Disable all GUI if scoped registries are not supported. + GUI.enabled = PackageManagerResolver.ScopedRegistriesSupported; + + GUILayout.BeginHorizontal(); + GUILayout.Label("Add package registries", EditorStyles.boldLabel); + settings.enable = EditorGUILayout.Toggle(settings.enable); + GUILayout.EndHorizontal(); + GUILayout.Label("When this option is enabled, Unity Package Manager registries " + + "discovered by this plugin will be added to the project's manifest. " + + "This allows Unity packages from additional sources to be " + + "discovered and managed by the Unity Package Manager."); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Prompt to add package registries", EditorStyles.boldLabel); + settings.promptToAddRegistries = EditorGUILayout.Toggle(settings.promptToAddRegistries); + GUILayout.EndHorizontal(); + GUILayout.Label("When this option is enabled, this plugin will prompt for confirmation " + + "before adding Unity Package Manager registries to the project's " + + "manifest."); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Prompt to migrate packages", EditorStyles.boldLabel); + settings.promptToMigratePackages = EditorGUILayout.Toggle(settings.promptToMigratePackages); + GUILayout.EndHorizontal(); + GUILayout.Label("When this option is enabled, this plugin will search the Unity Package " + + "Manager (UPM) for available packages that are currently installed in " + + "the project in the `Assets` directory that have equivalent or newer " + + "versions available on UPM and prompt to migrate these packages."); + + settings.analyticsSettings.RenderGui(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Verbose logging", EditorStyles.boldLabel); + settings.verboseLoggingEnabled = EditorGUILayout.Toggle(settings.verboseLoggingEnabled); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Use project settings", EditorStyles.boldLabel); + settings.useProjectSettings = EditorGUILayout.Toggle(settings.useProjectSettings); + GUILayout.EndHorizontal(); + + GUILayout.Space(10); + + if (GUILayout.Button("Reset to Defaults")) { + // Load default settings into the dialog but preserve the state in the user's + // saved preferences. + var backupSettings = new Settings(); + PackageManagerResolver.RestoreDefaultSettings(); + PackageManagerResolver.analytics.Report("settings/reset", "Settings Reset"); + LoadSettings(); + backupSettings.Save(); + } + + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Cancel")) { + PackageManagerResolver.analytics.Report("settings/cancel", "Settings Cancel"); + Close(); + } + if (GUILayout.Button("OK")) { + PackageManagerResolver.analytics.Report( + "settings/save", + new KeyValuePair[] { + new KeyValuePair( + "enabled", + PackageManagerResolver.Enable.ToString()), + new KeyValuePair( + "promptToAddRegistries", + PackageManagerResolver.PromptToAddRegistries.ToString()), + new KeyValuePair( + "promptToMigratePackages", + PackageManagerResolver.PromptToMigratePackages.ToString()), + new KeyValuePair( + "verboseLoggingEnabled", + PackageManagerResolver.VerboseLoggingEnabled.ToString()), + }, + "Settings Save"); + settings.Save(); + Close(); + + PackageManagerResolver.CheckRegistries(); + } + GUILayout.EndHorizontal(); + + EditorGUILayout.EndScrollView(); + GUILayout.EndVertical(); + + // Re-enable GUI + GUI.enabled = true; + } +} + +} // namespace Google + diff --git a/source/PackageManagerResolver/src/VersionNumber.cs b/source/PackageManagerResolver/src/VersionNumber.cs new file mode 100644 index 00000000..d112d2be --- /dev/null +++ b/source/PackageManagerResolver/src/VersionNumber.cs @@ -0,0 +1,42 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Google { + using System; + + using UnityEditor; + + /// + /// Get the version number of this plugin. + /// + public class PackageManagerResolverVersionNumber { + + /// + /// Version number, patched by the build process. + /// + private const string VERSION_STRING = "1.2.186"; + + /// + /// Cached version structure. + /// + private static Version value = new Version(VERSION_STRING); + + /// + /// Get the version number. + /// + public static Version Value { get { return value; } } + } +} diff --git a/source/PackageManagerResolver/src/XmlPackageManagerRegistries.cs b/source/PackageManagerResolver/src/XmlPackageManagerRegistries.cs new file mode 100644 index 00000000..9dd3909c --- /dev/null +++ b/source/PackageManagerResolver/src/XmlPackageManagerRegistries.cs @@ -0,0 +1,202 @@ +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + + +namespace Google { + using System; + using System.Collections.Generic; + using System.IO; + using System.Text.RegularExpressions; + using System.Xml; + using Google; + using UnityEditor; + + /// + /// Parses XML declared Unity Package Manager (UPM) registries required by a Unity plugin into + /// the XmlRegistries.Registry class. + /// + internal class XmlPackageManagerRegistries { + + /// + /// Set of regular expressions that match files which contain dependency + /// specifications. + /// + internal static readonly HashSet fileRegularExpressions = new HashSet { + new Regex(@".*[/\\]Editor[/\\].*Registries\.xml$") + }; + + /// + /// Paths to search for files. + /// + internal static readonly string[] fileSearchPaths = new string[] { "Assets", "Packages"}; + + /// + /// Label applied to registries. + /// + internal static readonly string REGISTRIES_LABEL = "gumpr_registries"; + + /// + /// Asset managed by this module. + /// + private static readonly string UPM_REGISTRIES = "Package Manager Registries"; + + /// + /// Registries read from files indexed by URL. + /// + internal Dictionary Registries; + + /// + /// Construct an empty XML UPM registries reader. + /// + public XmlPackageManagerRegistries() { Clear(); } + + /// + /// Clear the cached registries. + /// + internal void Clear() { + Registries = new Dictionary(); + } + + /// + /// Determines whether a filename matches an XML registries file. + /// + /// Filename to check. + /// true if it is a match, false otherwise. + internal static bool IsRegistriesFile(string filename) { + foreach (var regex in fileRegularExpressions) { + if (regex.Match(filename).Success) { + return true; + } + } + return false; + } + + /// + /// Find all XML declared registries files. + /// + /// Set of XML registries filenames in the project. + private IEnumerable FindFiles() { + var foundFiles = new HashSet( + VersionHandlerImpl.SearchAssetDatabase( + assetsFilter: "Registries t:TextAsset", + filter: IsRegistriesFile, + directories: fileSearchPaths)); + foundFiles.UnionWith(VersionHandlerImpl.SearchAssetDatabase( + assetsFilter: "l:" + REGISTRIES_LABEL, + directories: fileSearchPaths)); + return foundFiles; + } + + /// + /// Read XML declared registries. + /// + /// File to read. + /// Logger class. + /// true if the file was read successfully, false otherwise. + internal bool Read(string filename, Logger logger) { + PackageManagerRegistry upmRegistry = null; + logger.Log(String.Format("Reading {0} XML file {1}", UPM_REGISTRIES, filename), + level: LogLevel.Verbose); + if (!XmlUtilities.ParseXmlTextFileElements( + filename, logger, + (reader, elementName, isStart, parentElementName, elementNameStack) => { + if (elementName == "registries" && parentElementName == "") { + return true; + } else if (elementName == "registry" && + parentElementName == "registries" && + isStart) { + upmRegistry = new PackageManagerRegistry() { + Name = reader.GetAttribute("name") ?? "", + Url = reader.GetAttribute("url") ?? "", + TermsOfService = reader.GetAttribute("termsOfService") ?? "", + PrivacyPolicy = reader.GetAttribute("privacyPolicy") ?? "", + CreatedBy = String.Format("{0}:{1}", filename, + reader.LineNumber) + }; + return true; + } else if (elementName == "scopes" && + parentElementName == "registry") { + if (isStart) upmRegistry.Scopes = new List(); + return true; + } else if (elementName == "scope" && + parentElementName == "scopes" && + !(String.IsNullOrEmpty(upmRegistry.Name) || + String.IsNullOrEmpty(upmRegistry.Url))) { + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + upmRegistry.Scopes.Add(reader.ReadContentAsString()); + } + return true; + } else if (elementName == "registry" && + parentElementName == "registries" && + !isStart) { + if (!(String.IsNullOrEmpty(upmRegistry.Name) || + String.IsNullOrEmpty(upmRegistry.Url) || + upmRegistry.Scopes.Count == 0)) { + PackageManagerRegistry existingRegistry; + if (!Registries.TryGetValue(upmRegistry.Url, out existingRegistry)) { + Registries[upmRegistry.Url] = upmRegistry; + } else if (!existingRegistry.Equals(upmRegistry)) { + logger.Log( + String.Format( + "{0} for URL '{1}' called '{2}' was already read " + + "from '{3}'.\n" + + "{0} from '{4}' will be ignored.", + UPM_REGISTRIES, upmRegistry.Url, upmRegistry.Name, + existingRegistry.CreatedBy, filename), + level: LogLevel.Warning); + } + } else { + logger.Log( + String.Format( + "Malformed {0} for registry {1} " + + "found in {2}.\n" + + "All {0} will be ignored in {2}.", + UPM_REGISTRIES, upmRegistry.ToString(), filename), + level: LogLevel.Error); + return false; + } + return true; + } + return false; + })) { + return false; + } + return true; + } + + /// + /// Find and read all XML declared registries. + /// + /// Logger class. + /// true if all files were read successfully, false otherwise. + public bool ReadAll(Logger logger) { + bool success = true; + Clear(); + foreach (var filename in FindFiles()) { + if (!Read(filename, logger)) { + logger.Log(String.Format("Unable to read {0} from {1}.\n" + + "{0} in this file will be ignored.", + UPM_REGISTRIES, filename), + level: LogLevel.Error); + success = false; + } + } + return success; + } + } +} + + diff --git a/source/PackageManagerResolver/test/PackageManagerClientIntegrationTests.csproj b/source/PackageManagerResolver/test/PackageManagerClientIntegrationTests.csproj new file mode 100644 index 00000000..c3e407bb --- /dev/null +++ b/source/PackageManagerResolver/test/PackageManagerClientIntegrationTests.csproj @@ -0,0 +1,65 @@ + + + + Debug + AnyCPU + {00A0DB5E-1120-24C9-331A-BE692C1F7C01} + Library + Google.PackageManagerClientIntegrationTests + Google.PackageManagerClientIntegrationTests + v2.0 + 1.2 + 12.0.0 + 2.0 + + + True + full + False + bin\Debug + DEBUG; + prompt + 4 + False + + + none + True + bin\Release + prompt + 4 + False + + + + $(UnityHintPath)/UnityEditor.dll + + + $(UnityHintPath)/UnityEngine.dll + + + + + + + + + + + {5378B37A-887E-49ED-A8AE-42FA843AA9DC} + VersionHandler + + + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + VersionHandlerImpl + + + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040} + IntegrationTester + + + {77EBE819-CBE6-4CA8-A791-ED747EA29D30} + PackageManagerResolver + + + diff --git a/source/PackageManagerResolver/test/PackageManagerClientIntegrationTests/PackageManagerClientIntegrationTests.cs b/source/PackageManagerResolver/test/PackageManagerClientIntegrationTests/PackageManagerClientIntegrationTests.cs new file mode 100644 index 00000000..b4b81f05 --- /dev/null +++ b/source/PackageManagerResolver/test/PackageManagerClientIntegrationTests/PackageManagerClientIntegrationTests.cs @@ -0,0 +1,353 @@ +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using System.Collections; +using System.Reflection; +using System.IO; + +using Google; + +namespace Google.PackageManagerClientIntegrationTests { + +/// +/// Integration tests for PackageManagerClient. +/// +public static class PackageManagerClientTests { + + /// + /// Whether the Unity Package Manager is available in the current version of Unity. + /// + private static bool UpmAvailable { + get { return ExecutionEnvironment.VersionMajorMinor >= 2017.3; } + } + + /// + /// Whether the SearchAll method is available in the Unity Package Manager. + /// + private static bool UpmSearchAllAvailable { + get { return ExecutionEnvironment.VersionMajorMinor >= 2018.0; } + } + + /// + /// Determine whether the available method is returning the package manager in the current + /// version of Unity. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestAvailable(IntegrationTester.TestCase testCase, + Action testCaseComplete) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + if (UpmAvailable != PackageManagerClient.Available) { + testCaseResult.ErrorMessages.Add(String.Format("PackageManagerClient.Available " + + "returned {0}, expected {1}", + PackageManagerClient.Available, + UpmAvailable)); + } + testCaseComplete(testCaseResult); + } + + /// + /// Convert a list of packages to a list of strings. + /// + /// List of PackageInfo instances to convert to strings. + /// List of string representation of the specified packages. + private static List PackageInfoListToStringList( + IEnumerable packageInfos) { + var packageInfosAsStrings = new List(); + if (packageInfos != null) { + foreach (var pkg in packageInfos) packageInfosAsStrings.Add(pkg.ToString()); + } + return packageInfosAsStrings; + } + + /// + /// Convert a list of packages to a list of string package names. + /// + /// List of PackageInfo instances to convert to strings. + /// List of package names of the specified packages. + private static List PackageInfoListToNameList( + IEnumerable packageInfos) { + var packageInfosAsStrings = new List(); + if (packageInfos != null) { + foreach (var pkg in packageInfos) packageInfosAsStrings.Add(pkg.Name); + } + return packageInfosAsStrings; + } + + /// + /// Make sure a set of package names are present in the specified list of PackageInfo objects. + /// + /// List of package names expected in the list. + /// List of PackageInfo instances to search. + /// TestCaseResult to add an error message to if the expected + /// packages aren't found in the packageInfos list. + /// String to add to the start of the error message if + /// expectedPackageNames are not in packageInfos. + private static void CheckPackageNamesInPackageInfos( + List expectedPackageNames, + IEnumerable packageInfos, + IntegrationTester.TestCaseResult testCaseResult, + string errorMessagePrefix) { + var packageNames = PackageInfoListToNameList(packageInfos); + if (!(new HashSet(packageNames)).IsSupersetOf(expectedPackageNames)) { + testCaseResult.ErrorMessages.Add(String.Format( + "{0}, package names [{1}] not found in:\n{2}\n", + errorMessagePrefix, String.Join(", ", expectedPackageNames.ToArray()), + String.Join("\n", packageNames.ToArray()))); + } + } + + /// + /// List packages installed in the project. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestListInstalledPackages( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + PackageManagerClient.ListInstalledPackages((result) => { + // Unity 2017.x doesn't install any default packages. + if (ExecutionEnvironment.VersionMajorMinor >= 2018.0) { + // Make sure a subset of the default packages installed in all a newly created + // Unity project are present in the returned list. + CheckPackageNamesInPackageInfos( + new List() { + "com.unity.modules.audio", + "com.unity.modules.physics" + }, + result.Packages, testCaseResult, "Found an unexpected set of packages"); + } + var message = String.Format( + "Error: '{0}', PackageInfos:\n{1}\n", + result.Error, + String.Join("\n", PackageInfoListToStringList(result.Packages).ToArray())); + if (!String.IsNullOrEmpty(result.Error.ToString())) { + testCaseResult.ErrorMessages.Add(message); + } else { + UnityEngine.Debug.Log(message); + } + testCaseComplete(testCaseResult); + }); + } + + /// + /// Search for all available packages. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestSearchAvailablePackagesAll( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + PackageManagerClient.SearchAvailablePackages( + (result) => { + // Make sure common optional Unity packages are returned in the search result. + if (UpmSearchAllAvailable) { + CheckPackageNamesInPackageInfos( + new List() { + "com.unity.2d.animation", + "com.unity.test-framework" + }, + result.Packages, testCaseResult, + "SearchAvailablePackages returned an unexpected set of packages"); + } + + var message = String.Format( + "Error: '{0}', PackageInfos:\n{1}\n", result.Error, + String.Join("\n", PackageInfoListToStringList(result.Packages).ToArray())); + if (!String.IsNullOrEmpty(result.Error.ToString())) { + testCaseResult.ErrorMessages.Add(message); + } else { + UnityEngine.Debug.Log(message); + } + testCaseComplete(testCaseResult); + }); + } + + /// + /// Search for a set of available packages. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestSearchAvailablePackages( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + var progressLines = new List(); + PackageManagerClient.SearchAvailablePackages( + new [] { + "com.unity.ads", + "com.unity.analytics@2.0.13" + }, + (result) => { + var expectedPackageNameByQuery = UpmAvailable ? + new Dictionary() { + {"com.unity.ads", "com.unity.ads"}, + {"com.unity.analytics@2.0.13", "com.unity.analytics"}, + } : new Dictionary(); + + // Make sure all expected queries were performed. + var queriesPerformed = new HashSet(result.Keys); + if (!queriesPerformed.SetEquals(expectedPackageNameByQuery.Keys)) { + testCaseResult.ErrorMessages.Add( + String.Format( + "Search returned a subset of queries [{0}] vs. expected [{1}]", + String.Join(", ", (new List(queriesPerformed)).ToArray()), + String.Join(", ", (new List( + expectedPackageNameByQuery.Keys)).ToArray()))); + } + + var packageResults = new List(); + foreach (var kv in result) { + var searchQuery = kv.Key; + var searchResult = kv.Value; + packageResults.Add(String.Format( + "{0}, Error: '{1}':\n{2}\n", searchQuery, searchResult.Error, + String.Join("\n", + PackageInfoListToStringList(searchResult.Packages).ToArray()))); + + if (!String.IsNullOrEmpty(searchResult.Error.ToString())) { + testCaseResult.ErrorMessages.Add( + String.Format("Failed when searching for '{0}', Error '{1}'", + searchQuery, searchResult.Error.ToString())); + } + + // Make sure returned packages match the search pattern. + string expectedPackageName; + if (expectedPackageNameByQuery.TryGetValue(searchQuery, + out expectedPackageName)) { + CheckPackageNamesInPackageInfos( + new List() { expectedPackageName }, searchResult.Packages, + testCaseResult, + String.Format("Returned an unexpected list of for search query '{0}'", + searchQuery)); + } else { + testCaseResult.ErrorMessages.Add( + String.Format("Unexpected search result returned '{0}'", searchQuery)); + } + } + + // Make sure progress was reported. + if (progressLines.Count == 0) { + testCaseResult.ErrorMessages.Add("No progress reported"); + } + + var message = String.Format(String.Join("\n", packageResults.ToArray())); + if (testCaseResult.ErrorMessages.Count == 0) { + UnityEngine.Debug.Log(message); + } else { + testCaseResult.ErrorMessages.Add(message); + } + testCaseComplete(testCaseResult); + }, + progress: (value, item) => { + progressLines.Add(String.Format("Progress: {0}: {1}", value, item)); + }); + } + + /// + /// Check a package manager change result for errors. + /// + /// Error to check. + /// Name of the changed package. + /// Expected installed / removed package name. + /// TestCaseResult to add an error message to if the result + /// indicates a failure or doesn't match the expected package name. + /// String description of the result. + private static string CheckChangeResult( + PackageManagerClient.Error error, string packageName, + string expectedPackageName, IntegrationTester.TestCaseResult testCaseResult) { + var message = String.Format("Error '{0}', Package Installed '{1}'", error, packageName); + if (!String.IsNullOrEmpty(error.ToString())) { + testCaseResult.ErrorMessages.Add(message); + } + + if (packageName != expectedPackageName) { + testCaseResult.ErrorMessages.Add(String.Format( + "Unexpected package installed '{0}' vs. '{1}', Error '{2}'", + packageName, expectedPackageName, error)); + } + return message; + } + + /// + /// Add a package. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestAddPackage( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + const string installPackage = "com.unity.analytics"; + PackageManagerClient.AddPackage( + installPackage, + (result) => { + var message = UpmAvailable ? CheckChangeResult( + result.Error, result.Package != null ? result.Package.Name : null, + installPackage, testCaseResult) : ""; + if (testCaseResult.ErrorMessages.Count == 0) { + UnityEngine.Debug.Log(message); + } + testCaseComplete(testCaseResult); + }); + } + + /// + /// Add and remove a package. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestAddAndRemovePackage( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + const string packageToModify = "com.unity.ads"; + PackageManagerClient.AddPackage( + packageToModify, + (result) => { + if (UpmAvailable) { + CheckChangeResult(result.Error, + result.Package != null ? result.Package.Name : null, + packageToModify, testCaseResult); + } + }); + + PackageManagerClient.RemovePackage( + packageToModify, + (result) => { + var message = UpmAvailable ? + CheckChangeResult(result.Error, result.PackageId, packageToModify, + testCaseResult) : ""; + if (testCaseResult.ErrorMessages.Count == 0) { + UnityEngine.Debug.Log(message); + } + testCaseComplete(testCaseResult); + }); + } +} + + +} diff --git a/source/PackageManagerResolver/test/PackageMigratorIntegrationTests.csproj b/source/PackageManagerResolver/test/PackageMigratorIntegrationTests.csproj new file mode 100644 index 00000000..8b99f876 --- /dev/null +++ b/source/PackageManagerResolver/test/PackageMigratorIntegrationTests.csproj @@ -0,0 +1,65 @@ + + + + Debug + AnyCPU + {4DBDEE33-4B6C-A866-93FE-04C15486BB03} + Library + Google.PackageMigratorIntegrationTests + Google.PackageMigratorIntegrationTests + v2.0 + 1.2 + 12.0.0 + 2.0 + + + True + full + False + bin\Debug + DEBUG; + prompt + 4 + False + + + none + True + bin\Release + prompt + 4 + False + + + + $(UnityHintPath)/UnityEditor.dll + + + $(UnityHintPath)/UnityEngine.dll + + + + + + + + + + + {5378B37A-887E-49ED-A8AE-42FA843AA9DC} + VersionHandler + + + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + VersionHandlerImpl + + + {DBD8D4D9-61AC-4E75-8CDC-CABE7033A040} + IntegrationTester + + + {77EBE819-CBE6-4CA8-A791-ED747EA29D30} + PackageManagerResolver + + + diff --git a/source/PackageManagerResolver/test/PackageMigratorIntegrationTests/PackageMigratorIntegrationTests.cs b/source/PackageManagerResolver/test/PackageMigratorIntegrationTests/PackageMigratorIntegrationTests.cs new file mode 100644 index 00000000..41c7a283 --- /dev/null +++ b/source/PackageManagerResolver/test/PackageMigratorIntegrationTests/PackageMigratorIntegrationTests.cs @@ -0,0 +1,212 @@ +// +// Copyright (C) 2020 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Remove this define when EDM with package migration is available on the Unity Package Manager. +#define EDM_WITH_MIGRATION_NOT_AVAILABLE_ON_UPM + +using System; +using System.Collections.Generic; + +using Google; + +namespace Google.PackageMigratorIntegrationTests { + +/// +/// Integration tests for PackageMigrator. +/// +public static class PackageManagerTests { + + /// + /// Initialize logging and expose GPR to UPM. + /// + [IntegrationTester.Initializer] + public static void Initialize() { + // Enable verbose logging. + PackageManagerResolver.logger.Level = LogLevel.Verbose; + + // Ensure the game package registry is added for the test. + PackageManagerResolver.UpdateManifest( + PackageManagerResolver.ManifestModificationMode.Add, + promptBeforeAction: false, + showDisableButton: false); + } + + /// + /// If UPM scoped registries aren't available, expect a task failure or report an error and + /// complete the specified test. + /// + /// Error string returned by a completed task. + /// Test case result to update. + /// Called when the test case is complete. + /// true if the task is complete, false otherwise. + private static bool CompleteIfNotAvailable( + string completionError, + IntegrationTester.TestCaseResult testCaseResult, + Action testCaseComplete) { + // If scoped registries support isn't available, expect this to fail. + if (!(PackageManagerClient.Available && + PackageManagerResolver.ScopedRegistriesSupported)) { + if (String.IsNullOrEmpty(completionError)) { + testCaseResult.ErrorMessages.Add("Expected failure but returned no error"); + } + testCaseComplete(testCaseResult); + return true; + } + return false; + } + + /// + /// Test searching for an EDM package to migrate that is the same version or newer than the + /// installed package. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestFindPackagesToMigrate( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + + // Collect progress reports. + var progressLog = new List>(); + PackageMigrator.PackageMap.FindPackagesProgressDelegate reportFindProgress = + (progressValue, description) => { + UnityEngine.Debug.Log(String.Format("Find progress {0}, {1}", progressValue, + description)); + progressLog.Add(new KeyValuePair(progressValue, description)); + }; + + PackageMigrator.PackageMap.FindPackagesToMigrate((error, packageMaps) => { + if (CompleteIfNotAvailable(error, testCaseResult, testCaseComplete)) return; + + if (!String.IsNullOrEmpty(error)) { + testCaseResult.ErrorMessages.Add(String.Format("Failed with error {0}", error)); + } + + var packageMapStrings = new List(); + var packageMapsByUpmName = new Dictionary(); + foreach (var packageMap in packageMaps) { + packageMapsByUpmName[packageMap.AvailablePackageManagerPackageInfo.Name] = + packageMap; + packageMapStrings.Add(packageMap.ToString()); + } + + // Version-less mapping of UPM package name to Version Handler package names. + var expectedVhNameAndUpmNames = new KeyValuePair[] { + new KeyValuePair("External Dependency Manager", + "com.google.external-dependency-manager"), + new KeyValuePair("Firebase Authentication", + "com.google.firebase.auth") + }; + foreach (var vhNameAndUpmName in expectedVhNameAndUpmNames) { + string expectedVhName = vhNameAndUpmName.Key; + string expectedUpmName = vhNameAndUpmName.Value; + PackageMigrator.PackageMap packageMap; + if (packageMapsByUpmName.TryGetValue(expectedUpmName, out packageMap)) { + if (packageMap.VersionHandlerPackageName != expectedVhName) { + testCaseResult.ErrorMessages.Add(String.Format( + "Unexpected Version Handler package name '{0}' vs. '{1}' for '{2}'", + packageMap.VersionHandlerPackageName, expectedVhName, + expectedUpmName)); + } + if (packageMap.AvailablePackageManagerPackageInfo.CalculateVersion() < + packageMap.VersionHandlerPackageCalculatedVersion) { + testCaseResult.ErrorMessages.Add(String.Format( + "Returned older UPM package {0} than VH package {1} for " + + "{2} --> {3}", + packageMap.AvailablePackageManagerPackageInfo.Version, + packageMap.VersionHandlerPackageVersion, expectedVhName, + expectedUpmName)); + } + } else { + testCaseResult.ErrorMessages.Add(String.Format( + "Package map {0} --> {1} not found.", expectedVhName, expectedUpmName)); + } + } + + if (packageMaps.Count != expectedVhNameAndUpmNames.Length) { + testCaseResult.ErrorMessages.Add( + String.Format("Migrator returned unexpected package maps:\n{0}", + String.Join("\n", packageMapStrings.ToArray()))); + } + + if (progressLog.Count == 0) { + testCaseResult.ErrorMessages.Add("No progress updates"); + } + testCaseComplete(testCaseResult); + }, reportFindProgress); + } + + /// + /// Test migration. + /// + /// Object executing this method. + /// Called when the test case is complete. + [IntegrationTester.TestCase] + public static void TestMigration( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { +#if EDM_WITH_MIGRATION_NOT_AVAILABLE_ON_UPM + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { Skipped = true }); + return; +#endif + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + PackageMigrator.TryMigration((error) => { + if (CompleteIfNotAvailable(error, testCaseResult, testCaseComplete)) return; + if (!String.IsNullOrEmpty(error)) { + testCaseResult.ErrorMessages.Add(String.Format( + "Migration failed with error {0}", error)); + } + + // Make sure only expected version handler packages are still installed. + var expectedVersionHandlerPackages = new HashSet() { + "Firebase Realtime Database" + }; + var manifestsByPackageName = new HashSet( + VersionHandlerImpl.ManifestReferences. + FindAndReadManifestsInAssetsFolderByPackageName().Keys); + manifestsByPackageName.ExceptWith(expectedVersionHandlerPackages); + if (manifestsByPackageName.Count > 0) { + testCaseResult.ErrorMessages.Add(String.Format( + "Unexpected version handler packages found in the project:\n{0}", + (new List(manifestsByPackageName)).ToArray())); + } + + // Make sure the expected UPM packages are installed. + PackageManagerClient.ListInstalledPackages((listResult) => { + var installedPackageNames = new HashSet(); + foreach (var pkg in listResult.Packages) { + installedPackageNames.Add(pkg.Name); + } + + // Make sure expected UPM packages are installed. + var expectedPackageNames = new List() { + "com.google.external-dependency-manager", + "com.google.firebase.auth" + }; + if (installedPackageNames.IsSupersetOf(expectedPackageNames)) { + testCaseResult.ErrorMessages.Add(String.Format( + "Expected packages [{0}] not installed", + String.Join(", ", expectedPackageNames.ToArray()))); + } + + testCaseComplete(testCaseResult); + }); + }); + } +} + +} diff --git a/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseAuth_version-6.13.0_manifest.txt b/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseAuth_version-6.13.0_manifest.txt new file mode 100644 index 00000000..e69de29b diff --git a/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseAuth_version-6.13.0_manifest.txt.meta b/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseAuth_version-6.13.0_manifest.txt.meta new file mode 100644 index 00000000..a0ee6360 --- /dev/null +++ b/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseAuth_version-6.13.0_manifest.txt.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: 582ec7fc4a96a07e46c1bd207510d1b8 +labels: +- gvh_version-6.13.0 +- gvh +- gvh_manifest +- gvhp_manifestname-0Firebase Authentication +- gvhp_manifestname-FirebaseAuth +timeCreated: 1474401009 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseDatabase_version-999.999.999_manifest.txt b/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseDatabase_version-999.999.999_manifest.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseDatabase_version-999.999.999_manifest.txt @@ -0,0 +1 @@ + diff --git a/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseDatabase_version-999.999.999_manifest.txt.meta b/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseDatabase_version-999.999.999_manifest.txt.meta new file mode 100644 index 00000000..29a5a071 --- /dev/null +++ b/source/PackageManagerResolver/test/PackageMigratorIntegrationTestsUnityProject/Assets/Editor/FirebaseDatabase_version-999.999.999_manifest.txt.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: b99ef6d392c27dc76df26bcc8e887ba7 +labels: +- gvh_version-999.999.999 +- gvh +- gvh_manifest +- gvhp_manifestname-0Firebase Realtime Database +- gvhp_manifestname-FirebaseDatabase +timeCreated: 1474401009 +licenseType: Pro +TextScriptImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManagerRegistryTest.cs b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManagerRegistryTest.cs new file mode 100644 index 00000000..b89ea56a --- /dev/null +++ b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManagerRegistryTest.cs @@ -0,0 +1,183 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Google.PackageManagerResolver.Tests { + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.IO; + + using Google; + + /// + /// Tests the PackageManagerRegistry class. + /// + [TestFixture] + public class PackageManagerRegistryTest { + + /// + /// Construct a PackageManagerRegistry and use all accessors. + /// + [Test] + public void TestConstruct() { + var registry = new PackageManagerRegistry() { + Name = "Reg", + Url = "http://unity.reg.org/", + Scopes = new List { "org.foo.bar" }, + TermsOfService = "http://unity.reg.org/terms", + PrivacyPolicy = "http://unity.reg.org/privacy", + CreatedBy = "foo.xml:123", + CustomData = "hello world" + }; + Assert.That(registry.Name, Is.EqualTo("Reg")); + Assert.That(registry.Url, Is.EqualTo("http://unity.reg.org/")); + CollectionAssert.AreEquivalent(registry.Scopes, new List { "org.foo.bar" }); + Assert.That(registry.TermsOfService, Is.EqualTo("http://unity.reg.org/terms")); + Assert.That(registry.PrivacyPolicy, Is.EqualTo("http://unity.reg.org/privacy")); + Assert.That(registry.CreatedBy, Is.EqualTo("foo.xml:123")); + Assert.That(registry.CustomData, Is.EqualTo("hello world")); + } + + /// + /// Test object comparison and hash code. + /// + [Test] + public void TestCompareAndGetHashCode() { + var reg = new PackageManagerRegistry(); + Assert.That(reg.GetHashCode(), Is.EqualTo(0)); + + var reg1 = new PackageManagerRegistry() { + Name = "Reg", + Url = "http://reg1.org/", + Scopes = new List { "org.foo.bar" }, + TermsOfService = "http://reg1.org/terms", + PrivacyPolicy = "http://reg1.org/privacy", + CreatedBy = "foo.xml:123", + CustomData = "hello world" + }; + var reg2 = new PackageManagerRegistry() { + Name = "Reg", + Url = "http://reg1.org/", + Scopes = new List { "org.foo.bar" }, + TermsOfService = "http://reg1.org/terms", + PrivacyPolicy = "http://reg1.org/privacy", + CreatedBy = "foo.xml:123", + CustomData = "hello world" + }; + Assert.That(reg1.Equals(reg2), Is.EqualTo(true)); + Assert.That(reg1.GetHashCode(), Is.EqualTo(reg2.GetHashCode())); + + reg2.CreatedBy = "foo2.xml:111"; + Assert.That(reg1.Equals(reg2), Is.EqualTo(true)); + Assert.That(reg1.GetHashCode(), Is.EqualTo(reg2.GetHashCode())); + + reg2.Name = "reg2"; + Assert.That(reg1.Equals(reg2), Is.EqualTo(false)); + Assert.That(reg1.GetHashCode(), Is.Not.EqualTo(reg2.GetHashCode())); + + reg2.Name = reg1.Name; + reg2.Url = "http://reg2.org/"; + Assert.That(reg1.Equals(reg2), Is.EqualTo(false)); + Assert.That(reg1.GetHashCode(), Is.Not.EqualTo(reg2.GetHashCode())); + + reg2.Url = reg1.Url; + reg2.TermsOfService = "http://reg2.org/terms"; + Assert.That(reg1.Equals(reg2), Is.EqualTo(false)); + Assert.That(reg1.GetHashCode(), Is.Not.EqualTo(reg2.GetHashCode())); + + reg2.TermsOfService = reg1.TermsOfService; + reg2.PrivacyPolicy = "http://reg2.org/privacy"; + Assert.That(reg1.Equals(reg2), Is.EqualTo(false)); + Assert.That(reg1.GetHashCode(), Is.Not.EqualTo(reg2.GetHashCode())); + + reg2.PrivacyPolicy = reg1.PrivacyPolicy; + reg2.Scopes = null; + Assert.That(reg1.Equals(reg2), Is.EqualTo(false)); + Assert.That(reg1.GetHashCode(), Is.Not.EqualTo(reg2.GetHashCode())); + + reg2.Scopes = new List { "org.reg2" }; + Assert.That(reg1.Equals(reg2), Is.EqualTo(false)); + Assert.That(reg1.GetHashCode(), Is.Not.EqualTo(reg2.GetHashCode())); + + reg2.Scopes = reg1.Scopes; + reg2.CustomData = "hello from reg2"; + Assert.That(reg1.Equals(reg2), Is.EqualTo(false)); + Assert.That(reg1.GetHashCode(), Is.Not.EqualTo(reg2.GetHashCode())); + } + + /// + /// Convert a PackageManagerRegistry to a string representation. + /// + [Test] + public void TestToString() { + var registry = new PackageManagerRegistry() { + Name = "Reg", + Url = "http://unity.reg.org/", + Scopes = new List { "org.foo.bar", "org.foo.baz"}, + TermsOfService = "http://unity.reg.org/terms", + CreatedBy = "foo.xml:123", + CustomData = "hello world" + }; + Assert.That(registry.ToString(), + Is.EqualTo("name: Reg, url: http://unity.reg.org, " + + "scopes: [org.foo.bar, org.foo.baz]")); + } + + /// + /// Convert a list of PackageManagerRegistry instances to a list of strings. + /// + [Test] + public void TestToStringList() { + var registries = new PackageManagerRegistry[] { + new PackageManagerRegistry() { + Name = "foo", + Url = "http://foo.com/", + Scopes = new List() { "foo.bar" }, + }, + new PackageManagerRegistry() { + Name = "bar", + Url = "http://bar.com/" + } + }; + Assert.That(PackageManagerRegistry.ToStringList(registries), + Is.EqualTo(new List() { + "name: foo, url: http://foo.com, scopes: [foo.bar]", + "name: bar, url: http://bar.com, scopes: []" + })); + } + + /// + /// Convert a list of PackageManagerRegistry instances to a string. + /// + [Test] + public void TestListToString() { + var registries = new PackageManagerRegistry[] { + new PackageManagerRegistry() { + Name = "foo", + Url = "http://foo.com/", + Scopes = new List() { "foo.bar" }, + }, + new PackageManagerRegistry() { + Name = "bar", + Url = "http://bar.com/" + } + }; + Assert.That(PackageManagerRegistry.ToString(registries), + Is.EqualTo("name: foo, url: http://foo.com, scopes: [foo.bar]\n" + + "name: bar, url: http://bar.com, scopes: []")); + } + } +} diff --git a/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManagerResolverTests.asmdef b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManagerResolverTests.asmdef new file mode 100644 index 00000000..1826febe --- /dev/null +++ b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManagerResolverTests.asmdef @@ -0,0 +1,25 @@ +{ + "name": "Google.PackageManagerResolverTests", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll", + "Google.VersionHandler.dll", + "Google.VersionHandlerImpl.dll", + "Google.PackageManagerResolver.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManifestModifierTest.cs b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManifestModifierTest.cs new file mode 100644 index 00000000..2a937608 --- /dev/null +++ b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/PackageManifestModifierTest.cs @@ -0,0 +1,348 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Google.PackageManagerResolver.Tests { + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.IO; + + using Google; + + /// + /// Tests the PackageManifestModifier class. + /// + [TestFixture] + public class PackageManifestModifierTest { + + /// + /// Object under test. + /// + PackageManifestModifier modifier; + + /// + /// Setup for the test + /// + [SetUp] + public void Setup() { + // Delete the temporary manifest if it exists. + if (File.Exists(PackageManifestModifier.MANIFEST_FILE_PATH)) { + File.Delete(PackageManifestModifier.MANIFEST_FILE_PATH); + } + + // Create a modifier that uses a logs to the system console. + modifier = new PackageManifestModifier(); + modifier.Logger.Target = LogTarget.Console; + modifier.Logger.Level = LogLevel.Debug; + } + + /// + /// Read a project manifest. + /// + private string ReadManifest() { + return File.ReadAllText(PackageManifestModifier.MANIFEST_FILE_PATH); + } + + /// + /// Write a project manifest. + /// + /// JSON string to write to the manifest file.> + private void WriteManifest(string manifest) { + var manifestDirectory = Path.GetDirectoryName( + PackageManifestModifier.MANIFEST_FILE_PATH); + if (!Directory.Exists(manifestDirectory)) Directory.CreateDirectory(manifestDirectory); + File.WriteAllText(PackageManifestModifier.MANIFEST_FILE_PATH, manifest); + } + + const string MANIFEST_SIMPLE = + "{\n" + + " \"dependencies\": {\n" + + " \"com.bar.foo\": \"1.2.3\"\n" + + " },\n" + + " \"scopedRegistries\": [\n" + + " {\n" + + " \"name\": \"A UPM Registry\",\n" + + " \"url\": \"https://unity.foobar.com/",\n" + + " \"scopes\": [\n" + + " \"foobar.unity.voxels\"\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + + /// + /// Test manifest with a few different registries. + /// + const string MANIFEST_MULTI_REGISTRIES = + "{\n" + + " \"scopedRegistries\": [\n" + + " {\n" + + " \"name\": \"Reg1\",\n" + + " \"url\": \"https://reg1.com/",\n" + + " \"scopes\": [\n" + + " \"com.reg1.foo\",\n" + + " \"com.reg1.bar\"\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"name\": \"Reg1 Ext\",\n" + + " \"url\": \"https://reg1.com/",\n" + + " \"scopes\": [\n" + + " \"com.reg1.ext\"\n" + + " ]\n" + + " },\n" + + " {\n" + + " \"name\": \"Reg2\",\n" + + " \"url\": \"https://unity.reg2.com/",\n" + + " \"scopes\": [\n" + + " \"com.reg2.bish\"\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + + /// + /// Read a valid manifest. + /// + [Test] + public void TestReadManifestValid() { + WriteManifest(MANIFEST_SIMPLE); + Assert.That(modifier.GetManifestJson(), Is.Empty); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + Assert.That(modifier.GetManifestJson(), Is.EqualTo(MANIFEST_SIMPLE)); + + var expected = new Dictionary() { + { + "dependencies", + new Dictionary() { + {"com.bar.foo", "1.2.3"} + } + }, + { + "scopedRegistries", + new List>() { + new Dictionary() { + { "name", "A UPM Registry" }, + { "url", "https://unity.foobar.com/" }, + { + "scopes", + new List() { "foobar.unity.voxels" } + } + } + } + } + }; + CollectionAssert.AreEquivalent(modifier.manifestDict, expected); + } + + /// + /// Copy constructor + /// + [Test] + public void TestCopyConstructor() { + WriteManifest(MANIFEST_SIMPLE); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + + PackageManifestModifier copy = new PackageManifestModifier(modifier); + + CollectionAssert.AreEquivalent(copy.manifestDict, modifier.manifestDict); + Assert.That(copy.GetManifestJson(), Is.EqualTo(MANIFEST_SIMPLE)); + Assert.That(copy.GetManifestJson(), Is.EqualTo(modifier.GetManifestJson())); + } + + /// + /// Try reading a manifest that doesn't exist. + /// + [Test] + public void TestReadManifestMissing() { + Assert.That(modifier.ReadManifest(), Is.EqualTo(false)); + } + + /// + /// Try reading a manifest with invalid JSON. + /// + [Test] + public void TestReadManifestInvalid() { + WriteManifest("This is not valid JSON"); + Assert.That(modifier.ReadManifest(), Is.EqualTo(false)); + } + + /// + /// Try to retrieve registries from a modifier that hasn't read a manifest. + /// + [Test] + public void TestPackageManagerRegistriesWithNoManifestLoaded() { + Assert.That(modifier.PackageManagerRegistries.Count, Is.EqualTo(0)); + } + + /// + /// Parse registries from a manifest. + /// + [Test] + public void TestPackageManagerRegistries() { + WriteManifest(MANIFEST_MULTI_REGISTRIES); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + + var registries = modifier.PackageManagerRegistries; + + Assert.That(registries.ContainsKey("https://reg1.com/"), Is.EqualTo(true)); + Assert.That(registries.ContainsKey("https://unity.reg2.com/"), Is.EqualTo(true)); + + var reg1 = registries["https://reg1.com/"]; + Assert.That(reg1.Count, Is.EqualTo(2)); + Assert.That(reg1[0].Name, Is.EqualTo("Reg1")); + Assert.That(reg1[0].Url, Is.EqualTo("https://reg1.com/")); + CollectionAssert.AreEquivalent(reg1[0].Scopes, + new List() { "com.reg1.foo", "com.reg1.bar" }); + Assert.That(reg1[1].Name, Is.EqualTo("Reg1 Ext")); + Assert.That(reg1[1].Url, Is.EqualTo("https://reg1.com/")); + CollectionAssert.AreEquivalent( + reg1[1].Scopes, new List() { "com.reg1.ext" }); + + var reg2 = registries["https://unity.reg2.com/"]; + Assert.That(reg2.Count, Is.EqualTo(1)); + Assert.That(reg2[0].Name, Is.EqualTo("Reg2")); + Assert.That(reg2[0].Url, Is.EqualTo("https://unity.reg2.com/")); + CollectionAssert.AreEquivalent(reg2[0].Scopes, new List() { "com.reg2.bish" }); + } + + /// + /// Try adding registries when a manifest isn't loaded. + /// + [Test] + public void TestAddRegistriesWithNoManifestLoaded() { + Assert.That(modifier.AddRegistries(new List()), + Is.EqualTo(false)); + } + + /// + /// Add no registries to a manifest and write out the results. + /// + [Test] + public void TestAddRegistriesEmptyAndWriteManifest() { + WriteManifest(MANIFEST_MULTI_REGISTRIES); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + Assert.That(modifier.AddRegistries(new List()), + Is.EqualTo(true)); + Assert.That(modifier.WriteManifest(), Is.EqualTo(true)); + Assert.That(ReadManifest(), Is.EqualTo(MANIFEST_MULTI_REGISTRIES)); + } + + /// + /// Add some registries to the manifest and write out the results. + /// + [Test] + public void TestAddRegistriesAndWriteManifest() { + WriteManifest(MANIFEST_MULTI_REGISTRIES); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + Assert.That( + modifier.AddRegistries(new PackageManagerRegistry[] { + new PackageManagerRegistry() { + Name = "Reg1", + Url = "https://reg1.com/", + Scopes = new List() { "com.reg1.foo", "com.reg1.bar" } + }, + new PackageManagerRegistry() { + Name = "Reg1 Ext", + Url = "https://reg1.com/", + Scopes = new List() { "com.reg1.ext" } + }, + new PackageManagerRegistry() { + Name = "Reg2", + Url = "https://unity.reg2.com/", + Scopes = new List() { "com.reg2.bish" } + } + }), + Is.EqualTo(true)); + Assert.That(modifier.WriteManifest(), Is.EqualTo(true)); + Assert.That(ReadManifest(), Is.EqualTo(MANIFEST_MULTI_REGISTRIES)); + } + + /// + /// Try removing registries when the manifest isn't loaded. + /// + [Test] + public void TestRemoveRegistriesWithNoManifestLoader() { + Assert.That(modifier.RemoveRegistries(new List()), + Is.EqualTo(false)); + } + + /// + /// Remove no registries from a manifest and write out the results. + /// + [Test] + public void TestRemoveRegistriesEmptyAndWriteManifest() { + WriteManifest(MANIFEST_MULTI_REGISTRIES); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + Assert.That(modifier.RemoveRegistries(new List()), + Is.EqualTo(true)); + Assert.That(modifier.WriteManifest(), Is.EqualTo(true)); + Assert.That(ReadManifest(), Is.EqualTo(MANIFEST_MULTI_REGISTRIES)); + } + + /// + /// Remove non-existent registries from a manifest and write out the results. + /// + [Test] + public void TestRemoveRegistriesNonExistentAndWriteManifest() { + WriteManifest(MANIFEST_MULTI_REGISTRIES); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + Assert.That( + modifier.RemoveRegistries(new PackageManagerRegistry[] { + new PackageManagerRegistry { + Name = "Texture It", + Url = "http://unity.cooltextures.org/", + Scopes = new List() { "org.cooltextures.textureit" } + } + }), + Is.EqualTo(false)); + Assert.That(modifier.WriteManifest(), Is.EqualTo(true)); + Assert.That(ReadManifest(), Is.EqualTo(MANIFEST_MULTI_REGISTRIES)); + } + + /// + /// Remove registries from a manifest and write out the results. + /// + [Test] + public void TestRemoveRegistriesAndWriteManifest() { + WriteManifest(MANIFEST_MULTI_REGISTRIES); + Assert.That(modifier.ReadManifest(), Is.EqualTo(true)); + Assert.That( + modifier.RemoveRegistries(new PackageManagerRegistry[] { + new PackageManagerRegistry() { + Name = "Reg1 Ext", + Url = "https://reg1.com/", + Scopes = new List() { "com.reg1.ext" } + }, + }), + Is.EqualTo(true)); + Assert.That(modifier.WriteManifest(), Is.EqualTo(true)); + Assert.That( + ReadManifest(), + Is.EqualTo("{\n" + + " \"scopedRegistries\": [\n" + + " {\n" + + " \"name\": \"Reg2\",\n" + + " \"url\": \"https://unity.reg2.com/",\n" + + " \"scopes\": [\n" + + " \"com.reg2.bish\"\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}")); + } + } +} diff --git a/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/XmlPackageManagerRegistriesTest.cs b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/XmlPackageManagerRegistriesTest.cs new file mode 100644 index 00000000..3530b0d1 --- /dev/null +++ b/source/PackageManagerResolver/unit_tests/Assets/PackageManagerResolverTests/XmlPackageManagerRegistriesTest.cs @@ -0,0 +1,231 @@ +// +// Copyright (C) 2020 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace Google.PackageManagerResolver.Tests { + using NUnit.Framework; + using System; + using System.Collections.Generic; + using System.IO; + + using Google; + + /// + /// Tests the PackageManagerRegistry class. + /// + [TestFixture] + public class XmlPackageManagerRegistriesTest { + + /// + /// Name of the test configuration file. + /// + const string TEST_CONFIGURATION_FILENAME = "TestRegistries.xml"; + + /// + /// Object under test. + /// + private XmlPackageManagerRegistries registries; + + /// + /// Logger for this test. + /// + private Logger logger = new Logger() { + Target = LogTarget.Console, + Level = LogLevel.Debug + }; + + /// + /// Write to the test registries file. + /// + /// String to write to the file. + private void WriteRegistries(string configuration) { + if (File.Exists(TEST_CONFIGURATION_FILENAME)) File.Delete(TEST_CONFIGURATION_FILENAME); + File.WriteAllText(TEST_CONFIGURATION_FILENAME, configuration); + } + + /// + /// Setup for the test + /// + [SetUp] + public void Setup() { + registries = new XmlPackageManagerRegistries(); + } + + /// + /// Make sure that a constructed object is empty. + /// + [Test] + public void TestRegistriesEmpty() { + Assert.That(registries.Registries.Count, Is.EqualTo(0)); + } + + /// + /// Add some items to the registries, clear and validate it's empty. + /// + [Test] + public void TestClear() { + registries.Registries["http://foo.bar.com/"] = new PackageManagerRegistry() { + Name = "foobar", + Url = "http://foo.bar.com/", + Scopes = new List() { "com.bar" } + }; + Assert.That(registries.Registries.Count, Is.EqualTo(1)); + registries.Clear(); + Assert.That(registries.Registries.Count, Is.EqualTo(0)); + } + + /// + /// Determine whether a filename is a container of UPM registries. + /// + [Test] + public void TestIsRegistriesFile() { + Assert.That(XmlPackageManagerRegistries.IsRegistriesFile( + "Assets/SomeRegistries.xml"), + Is.EqualTo(false)); + Assert.That(XmlPackageManagerRegistries.IsRegistriesFile( + "Assets/MyPlugin/SomeRegistries.xml"), + Is.EqualTo(false)); + Assert.That(XmlPackageManagerRegistries.IsRegistriesFile( + "Assets/Editor/SomeRegistries.txt"), + Is.EqualTo(false)); + Assert.That(XmlPackageManagerRegistries.IsRegistriesFile( + "Assets/Editor/SomeRegistries.xml"), + Is.EqualTo(true)); + Assert.That(XmlPackageManagerRegistries.IsRegistriesFile( + "Assets\\Editor\\SomeRegistries.xml"), + Is.EqualTo(true)); + Assert.That(XmlPackageManagerRegistries.IsRegistriesFile( + "Assets/MyPlugin/Editor/SomeRegistries.xml"), + Is.EqualTo(true)); + Assert.That(XmlPackageManagerRegistries.IsRegistriesFile( + "Assets\\MyPlugin\\Editor\\SomeRegistries.xml"), + Is.EqualTo(true)); + } + + /// + /// Test Read() with a valid XML file. + /// + [Test] + public void TestRead() { + WriteRegistries("\n" + + " \n" + + " \n" + + " com.reg1\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " com.reg2.foo\n" + + " com.reg2.bar\n" + + " \n" + + " \n" + + "\n"); + Assert.That(registries.Read(TEST_CONFIGURATION_FILENAME, logger), Is.EqualTo(true)); + Assert.That(registries.Registries.Count, Is.EqualTo(2)); + CollectionAssert.AreEquivalent(registries.Registries.Keys, + new [] { "https://reg1.com/", "https://reg2.com/"}); + var reg1 = registries.Registries["https://reg1.com/"]; + Assert.That(reg1.Name, Is.EqualTo("Reg1")); + Assert.That(reg1.Url, Is.EqualTo("https://reg1.com/")); + Assert.That(reg1.TermsOfService, Is.EqualTo("https://reg1.com/terms")); + Assert.That(reg1.PrivacyPolicy, Is.EqualTo("https://reg1.com/privacy")); + CollectionAssert.AreEquivalent(reg1.Scopes, new [] { "com.reg1" } ); + var reg2 = registries.Registries["https://reg2.com/"]; + Assert.That(reg2.Name, Is.EqualTo("Reg2")); + Assert.That(reg2.Url, Is.EqualTo("https://reg2.com/")); + Assert.That(reg2.TermsOfService, Is.EqualTo("")); + CollectionAssert.AreEquivalent(reg2.Scopes, new [] { "com.reg2.foo", "com.reg2.bar" } ); + } + + /// + /// Test Read() with a configuration that uses the same registry URL multiple times. + /// + [Test] + public void TestReadDuplicateUrl() { + WriteRegistries("\n" + + " \n" + + " \n" + + " com.reg1\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " com.reg1\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " com.reg1.foobar\n" + + " \n" + + " \n" + + "\n"); + Assert.That(registries.Read(TEST_CONFIGURATION_FILENAME, logger), Is.EqualTo(true)); + Assert.That(registries.Registries.Count, Is.EqualTo(1)); + CollectionAssert.AreEquivalent(registries.Registries.Keys, + new [] { "https://reg1.com/" }); + var reg1 = registries.Registries["https://reg1.com/"]; + Assert.That(reg1.Name, Is.EqualTo("Reg1")); + Assert.That(reg1.Url, Is.EqualTo("https://reg1.com/")); + Assert.That(reg1.TermsOfService, Is.EqualTo("https://reg1.com/terms")); + CollectionAssert.AreEquivalent(reg1.Scopes, new [] { "com.reg1" } ); + } + + /// + /// Try reading a malformed configuration files. + /// + [Test] + public void TestReadBrokenConfigs() { + // Not XML. + WriteRegistries("this is not xml"); + Assert.That(registries.Read(TEST_CONFIGURATION_FILENAME, logger), Is.EqualTo(false)); + + // Invalid tag. + WriteRegistries(""); + Assert.That(registries.Read(TEST_CONFIGURATION_FILENAME, logger), Is.EqualTo(false)); + + // Missing url attribute. + WriteRegistries(""); + Assert.That(registries.Read(TEST_CONFIGURATION_FILENAME, logger), Is.EqualTo(false)); + + // Missing scopes block and scope entries. + WriteRegistries("\n" + + " \n" + + " \n" + + ""); + Assert.That(registries.Read(TEST_CONFIGURATION_FILENAME, logger), Is.EqualTo(false)); + + // Missing scope entries. + WriteRegistries("\n" + + " \n" + + " \n" + + " \n" + + ""); + Assert.That(registries.Read(TEST_CONFIGURATION_FILENAME, logger), Is.EqualTo(false)); + } + } +} diff --git a/source/PackageManagerTests/PackageManagerTests.csproj b/source/PackageManagerTests/PackageManagerTests.csproj deleted file mode 100644 index ee9608a7..00000000 --- a/source/PackageManagerTests/PackageManagerTests.csproj +++ /dev/null @@ -1,70 +0,0 @@ - - - - Debug - AnyCPU - {7E1CDCE1-1B39-48F6-9DEA-A714FD6654D2} - Library - PackageManagerTests - PackageManagerTests - v2.0 - 1.2 - 12.0.0 - 2.0 - - - True - full - False - bin\Debug - DEBUG; - prompt - 4 - False - - - none - True - bin\Release - prompt - 4 - False - - - ..\packages\NUnit.2.6.3\lib\ - - - - - - $(NUnityHintPath)/nunit.framework.dll - - - - - - - - - - - - - - - - - - - - - - - {8B0A2564-01ED-426B-AF33-33EED4A81828} - PackageManager - - - - - - diff --git a/source/PackageManagerTests/packages.config b/source/PackageManagerTests/packages.config deleted file mode 100644 index d4e241a2..00000000 --- a/source/PackageManagerTests/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/source/PackageManagerTests/src/Google.PackageManager.Tests/ControllerTests.cs b/source/PackageManagerTests/src/Google.PackageManager.Tests/ControllerTests.cs deleted file mode 100644 index 1bec5249..00000000 --- a/source/PackageManagerTests/src/Google.PackageManager.Tests/ControllerTests.cs +++ /dev/null @@ -1,339 +0,0 @@ -// -// Copyright (C) 2014 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Google.PackageManager.Tests { - using System.IO; - using PackageManager; - using NUnit.Framework; - using System.Collections.Generic; - using System; - - internal static class TestData { - public class MockEditorPrefs : IEditorPrefs { - public Dictionary data; - - public MockEditorPrefs() { - data = new Dictionary(); - } - - public void DeleteAll() { - data.Clear(); - } - - public void DeleteKey(string key) { - data.Remove(key); - } - - string GetValue(string key, object defaultValue) { - string tmp; - if (data.TryGetValue(key, out tmp)) { - return tmp; - } - return string.Format("{0}", defaultValue); - } - - public bool GetBool(string key, bool defaultValue = false) { - return bool.Parse(GetValue(key, defaultValue)); - } - - public float GetFloat(string key, float defaultValue = 0) { - return float.Parse(GetValue(key, defaultValue)); - } - - public int GetInt(string key, int defaultValue = 0) { - return int.Parse(GetValue(key, defaultValue)); - } - - public string GetString(string key, string defaultValue = "") { - return GetValue(key, defaultValue); - } - - public bool HasKey(string key) { - return data.ContainsKey(key); - } - - void SetValue(string key, object value) { - if (data.ContainsKey(key)) { - data[key] = string.Format("{0}", value); - } else { - data.Add(key, string.Format("{0}", value)); - } - } - - public void SetBool(string key, bool value) { - SetValue(key, value); - } - - public void SetFloat(string key, float value) { - SetValue(key, value); - } - - public void SetInt(string key, int value) { - SetValue(key, value); - } - - public void SetString(string key, string value) { - SetValue(key, value); - } - } - - public static IEditorPrefs editorPrefs = new MockEditorPrefs(); - - // Path to test data, contains a mock data. - public static string PATH { - get { - var dir = Environment.GetEnvironmentVariable("TEST_DATA_DIR"); - return dir != null ? dir : "../../testData"; - } - } - - public class MockFetcher : IUriDataFetcher { - string textResult; - ResponseCode rc; - public MockFetcher(string giveText, ResponseCode giveResponse) { - textResult = giveText; - rc = giveResponse; - } - - public ResponseCode BlockingFetchAsString(Uri uri, out string result) { - result = textResult; - return rc; - } - - public ResponseCode BlockingFetchAsBytes(Uri uri, out byte[] result) { - throw new NotImplementedException(); - } - } - - public class MockMultiFetcher : IUriDataFetcher { - List textResults = new List(); - List responses = new List(); - public int currentIndex = 0; - - public void ResetIndex() { - currentIndex = 0; - } - - public void AddResponse(string giveText, ResponseCode giveResponse) { - textResults.Add(giveText); - responses.Add(giveResponse); - } - - public ResponseCode BlockingFetchAsString(Uri uri, out string result) { - result = textResults[currentIndex]; - var responseCode = responses[currentIndex]; - ++currentIndex; - if (currentIndex >= textResults.Count) { - throw new Exception("TEST CASE EXCEPTION - Multi Fetch exceeded index"); - } - return responseCode; - } - - public ResponseCode BlockingFetchAsBytes(Uri uri, out byte[] result) { - throw new NotImplementedException(); - } - } - - public class MockDeterministicFetcher : IUriDataFetcher { - Dictionary urlToXml = new Dictionary(); - Dictionary uriToResponse = new Dictionary(); - public void AddResponse(string uri, string giveText, ResponseCode giveResponse) { - if (!urlToXml.ContainsKey(uri)) { - urlToXml.Add(uri, giveText); - } else { - urlToXml[uri] = giveText; - } - if (!uriToResponse.ContainsKey(uri)) { - uriToResponse.Add(uri, giveResponse); - } else { - uriToResponse[uri] = giveResponse; - } - } - public ResponseCode BlockingFetchAsString(Uri uri, out string result) { - if (urlToXml.TryGetValue(uri.AbsoluteUri, out result)) { - ResponseCode r; - uriToResponse.TryGetValue(uri.AbsoluteUri, out r); - Console.WriteLine(string.Format("MockDeterministicFetcher:\nASK:{0}" + - "\nRESP:{1}\nDATA:{2}", uri, r, result)); - return r; - } - result = null; - return ResponseCode.FETCH_ERROR; - } - - public ResponseCode BlockingFetchAsBytes(Uri uri, out byte[] result) { - throw new NotImplementedException(); - } - } - } - - /// - /// Test case set that exercises the PackageManagerController. - /// - [TestFixture] - public class ControllerTests { - TestData.MockDeterministicFetcher mockFetcher = new TestData.MockDeterministicFetcher(); - - [SetUp] - public void Setup() { - UnityController.SwapEditorPrefs(new TestData.MockEditorPrefs()); - LoggingController.testing = true; - TestableConstants.testcase = true; - TestableConstants.DefaultRegistryLocation = - new Uri(Path.GetFullPath( - Path.Combine(TestData.PATH, "registry/registry.xml"))) - .AbsoluteUri; - - string testRegXmlPath = TestableConstants.DefaultRegistryLocation; - mockFetcher.AddResponse((new Uri(testRegXmlPath)).AbsoluteUri, - File.ReadAllText((new Uri(testRegXmlPath)).AbsolutePath), - ResponseCode.FETCH_COMPLETE); - var rb = new RegistryManagerController.RegistryDatabase(); - rb.registryLocation.Add(TestableConstants.DefaultRegistryLocation); - UnityController.EditorPrefs.SetString(Constants.KEY_REGISTRIES, - rb.SerializeToXMLString()); - - var u = new Uri(Path.GetFullPath(Path.Combine(TestData.PATH,"registry2/registry.xml"))); - mockFetcher.AddResponse(u.AbsoluteUri, - File.ReadAllText(u.AbsolutePath), - ResponseCode.FETCH_COMPLETE); - - UriDataFetchController.SwapUriDataFetcher(mockFetcher); - UnityController.SwapEditorPrefs(TestData.editorPrefs); - - var rdb = new RegistryManagerController.RegistryDatabase(); - rdb.registryLocation.Add(TestableConstants.DefaultRegistryLocation); - - /// ISO 8601 format: yyyy-MM-ddTHH:mm:ssZ - rdb.lastUpdate = DateTime.UtcNow.ToString("o"); // "o" = ISO 8601 formatting - Console.Write(rdb.SerializeToXMLString()); - TestData.editorPrefs.SetString(Constants.KEY_REGISTRIES, rdb.SerializeToXMLString()); - - } - - /// - /// Tests the settings controller by toggleing the boolean settings and - /// ensuring the DownloadCache location is set by default. - /// - [Test] - public void TestSettingsController() { - // DowloadCachePath should start with a value (system dependent). - Assert.NotNull(SettingsController.DownloadCachePath, - "Why is download cache path null?"); - - // Set and Get test. - SettingsController.DownloadCachePath = Path.GetFullPath(TestData.PATH); - Assert.NotNull(SettingsController.DownloadCachePath, - "Why is download cache path null?"); - Assert.AreEqual(Path.GetFullPath(TestData.PATH), SettingsController.DownloadCachePath, - "Why is download cache path different?"); - - // Verbose logging Get/Set. - Assert.IsTrue(SettingsController.VerboseLogging, "VerboseLogging to start true."); - SettingsController.VerboseLogging = false; - Assert.IsFalse(SettingsController.VerboseLogging); - - // Show install files Get/Set. - Assert.IsTrue(SettingsController.ShowInstallFiles, "ShowInstallFiles to start true"); - SettingsController.ShowInstallFiles = false; - Assert.IsFalse(SettingsController.ShowInstallFiles); - } - - [Test] - public void TestRegistryManagerController() { - Assert.AreEqual(1, RegistryManagerController.AllWrappedRegistries.Count); - - var u = new Uri(Path.GetFullPath(Path.Combine(TestData.PATH, - "registry2/registry.xml"))); - Assert.AreEqual(ResponseCode.REGISTRY_ADDED, - RegistryManagerController.AddRegistry(u)); - Assert.AreEqual(2, RegistryManagerController.AllWrappedRegistries.Count); - - // Cannot add same uri. - Assert.AreEqual(ResponseCode.REGISTRY_ALREADY_PRESENT, - RegistryManagerController.AddRegistry(u)); - - // loading the database again should still be in same state - RegistryManagerController.LoadRegistryDatabase(); - Assert.AreEqual(2,RegistryManagerController.AllWrappedRegistries.Count); - - Assert.AreEqual(ResponseCode.REGISTRY_REMOVED, - RegistryManagerController.RemoveRegistry(u)); - - // Can't remove it a second time it won't be there. - Assert.AreEqual(ResponseCode.REGISTRY_NOT_FOUND, - RegistryManagerController.RemoveRegistry(u)); - } - - [Test] - public void TestPluginManagerController() { - RegistryManagerController.LoadRegistryDatabase(); - Assert.AreEqual(1,RegistryManagerController.AllWrappedRegistries.Count); - RegistryWrapper r = RegistryManagerController.AllWrappedRegistries[0]; - - var u = new Uri(Path.GetFullPath(Path.Combine( - TestData.PATH, "registry/com.google.unity.example/package-manifest.xml"))); - mockFetcher.AddResponse(u.AbsoluteUri, - File.ReadAllText(u.AbsolutePath), - ResponseCode.FETCH_COMPLETE); - - u = new Uri(Path.GetFullPath(Path.Combine( - TestData.PATH, - "registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/description.xml"))); - mockFetcher.AddResponse(u.AbsoluteUri, - File.ReadAllText(u.AbsolutePath), - ResponseCode.FETCH_COMPLETE); - - // Test ChangeRegistryUriIntoModuleUri. - var regU = new Uri(TestableConstants.DefaultRegistryLocation); - var modName = "apples-oranges"; - var metaLoc = PluginManagerController.ChangeRegistryUriIntoModuleUri(regU, modName); - Assert.IsTrue(metaLoc.AbsoluteUri.Contains(modName)); - Assert.IsTrue(metaLoc.AbsoluteUri.Contains(Constants.MANIFEST_FILE_NAME)); - - // Test GetPluginForRegistry. - var plugins = PluginManagerController.GetPluginsForRegistry(r, true); - Assert.AreEqual(1, plugins.Count); - var packagedPlugin = plugins[0]; - Assert.NotNull(packagedPlugin); - Assert.AreEqual(r.Model, packagedPlugin.ParentRegistry); - - // Test GenerateDescriptionUri. - var d = PluginManagerController.GenerateDescriptionUri(metaLoc, - packagedPlugin.MetaData); - Assert.IsTrue(d.AbsoluteUri.Contains(packagedPlugin.MetaData.artifactId)); - Assert.IsTrue(d.AbsoluteUri.Contains(packagedPlugin.MetaData.versioning.release)); - Assert.IsTrue(d.AbsoluteUri.Contains(Constants.DESCRIPTION_FILE_NAME)); - - plugins = PluginManagerController.GetPluginsForRegistry(null); - Assert.AreEqual(0, plugins.Count); - - // Test Refresh. - PluginManagerController.Refresh(r); - plugins = PluginManagerController.GetPluginsForRegistry(r); - Assert.AreEqual(1, plugins.Count); - packagedPlugin = plugins[0]; - Assert.NotNull(packagedPlugin); - Assert.AreEqual(r.Model, packagedPlugin.ParentRegistry); - } - - [Test] - public void TestProjectManagerController() { - // TODO - test refresh list of assets - // TODO - test IsPluginInstalledInProject - - } - } -} diff --git a/source/PackageManagerTests/src/Google.PackageManager.Tests/ModelTests.cs b/source/PackageManagerTests/src/Google.PackageManager.Tests/ModelTests.cs deleted file mode 100644 index dd81f9df..00000000 --- a/source/PackageManagerTests/src/Google.PackageManager.Tests/ModelTests.cs +++ /dev/null @@ -1,101 +0,0 @@ -// -// Copyright (C) 2014 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -namespace Google.PackageManager.Tests { - using System.IO; - using PackageManager; - using NUnit.Framework; - using System; - - [TestFixture] - public class PackageManagerModelTests { - /// - /// Tests root models are able to load from file. - /// Root models include: - /// - Repository - /// - Description - /// - PluginMetaData - /// - PackageExportSettings - /// - [Test] - public void TestLoadFromFile() { - string registryPath = Path.Combine(TestData.PATH,"registry/registry.xml"); - Registry registry = Registry.LoadFromFile(registryPath); - Assert.AreEqual("registry.google.unity",registry.groupId); - Assert.AreEqual("jarresolver-google-registry",registry.artifactId); - Assert.AreEqual("0.0.1.1",registry.version); - Assert.AreEqual(1482171836,registry.lastUpdated); - Assert.NotNull(registry.modules); - Assert.AreEqual(1,registry.modules.module.Count); - Assert.AreEqual("com.google.unity.example",registry.modules.module[0]); - - string barPluginPath = Path.Combine(TestData.PATH, - "registry/com.google.unity.example/package-manifest.xml"); - PluginMetaData pluginMetaData = PluginMetaData.LoadFromFile(barPluginPath); - Assert.AreEqual("com.google.unity.example",pluginMetaData.groupId); - Assert.AreEqual("gpm-example-plugin",pluginMetaData.artifactId); - Assert.AreEqual("unitypackage",pluginMetaData.packaging); - Assert.NotNull(pluginMetaData.versioning); - Assert.AreEqual("1.0.0.0",pluginMetaData.versioning.release); - Assert.NotNull(pluginMetaData.versioning.versions); - Assert.AreEqual(1,pluginMetaData.versioning.versions.Count); - Assert.AreEqual(0,pluginMetaData.lastUpdated); - - string barDescriptionPath = Path.Combine(TestData.PATH, - "registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/description.xml"); - PluginDescription description = PluginDescription.LoadFromFile(barDescriptionPath); - Assert.NotNull(description.languages); - Assert.AreEqual(1,description.languages.Count); - } - - [Test] - public void TestPackageDependencies() { - var pd = new PackageDependencies(); - pd.groupId = "my.group.id"; - pd.artifactId = "my-artifact-id"; - - var dep = new AndroidPackageDependency(); - dep.group = "com.google.android.gms"; - dep.artifact = "play-services-ads"; - dep.version = "LATEST"; - - var arg = new DependencyArgument(); - arg.packageIds.Add("extra-google-m2repository"); - arg.packageIds.Add("extra-google-m2repository"); - arg.repositories.Add("some-repository"); - - dep.args = arg; - pd.androidDependencies.Add(dep); - - var xml = pd.SerializeToXMLString(); - // these can come back out of order on inflate - so we remove them - xml = xml.Replace("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance/"",""); - xml = xml.Replace("xmlns:xsd=\"http://www.w3.org/2001/XMLSchema/"",""); - Console.WriteLine("Actual: "+xml+"\n\n"); - xml = xml.Substring(1); // strip BOM for test - var expectedXml = File.ReadAllText( - Path.Combine( - Path.Combine( - Path.GetFullPath(TestData.PATH), - "flatdeps"), - "group.id.example-artifact.gpm.deps.xml")); - // these can come back out of order on inflate - so we remove them - expectedXml = expectedXml.Replace("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance/"",""); - expectedXml = expectedXml.Replace("xmlns:xsd=\"http://www.w3.org/2001/XMLSchema/"",""); - Console.WriteLine("Expected: " + xml + "\n\n"); - Assert.AreEqual(expectedXml,xml); - } - } -} diff --git a/source/PackageManagerTests/testData/editor/PackageManagerPackager.cfg.xml b/source/PackageManagerTests/testData/editor/PackageManagerPackager.cfg.xml deleted file mode 100644 index 3442c869..00000000 --- a/source/PackageManagerTests/testData/editor/PackageManagerPackager.cfg.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - Krispy Bar Plugin - A bar as a plugin - Woot some description goes here - - - - Bar Plugin - krispy.co.bar - bar-plugin - 1.0.0.0 - GPM:krispy.co.bar:bar-plugin:1.0.0.0 - - - Assets/BarPlugin - Assets/BarPlugin/Editor - Assets/BarPlugin/Editor/BarGoogleDeps.cs - Assets/BarPlugin/Editor/SomeScript.cs - - - - - - - - Google Unity Package Manager - A package manager for plugins that make use of the JarResolver - A complete package management solution for developers who make use of the - JarResolver. Provides discoverability, install & clean removal - of plugins. - - - - Package Manager Plugin - com.google.unity - package-manager - 1.0.0.0 - GPM:com.google.unity:package-manager:1.0.0.0 - - - Assets/Editor - Assets/Editor/PackageManager.cs - Assets/Editor/PackageManagerModels.cs - Assets/Editor/PackageManagerPackager.cs - Assets/PlayServicesResolver - Assets/PlayServicesResolver/Editor - Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll - Assets/PlayServicesResolver/Editor/Google.JarResolver.dll - Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll - Assets/PlayServicesResolver/Editor/play-services-resolver.txt - - - - - diff --git a/source/PackageManagerTests/testData/flatdeps/group.id.example-artifact.gpm.deps.xml b/source/PackageManagerTests/testData/flatdeps/group.id.example-artifact.gpm.deps.xml deleted file mode 100644 index 06ce2d1f..00000000 --- a/source/PackageManagerTests/testData/flatdeps/group.id.example-artifact.gpm.deps.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - my.group.id - my-artifact-id - - - com.google.android.gms - play-services-ads - LATEST - - - extra-google-m2repository - extra-google-m2repository - - - some-repository - - - - - - \ No newline at end of file diff --git a/source/PackageManagerTests/testData/registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/description.xml b/source/PackageManagerTests/testData/registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/description.xml deleted file mode 100755 index f620ecb1..00000000 --- a/source/PackageManagerTests/testData/registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/description.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - GPM Example Plugin - A demonstration of a Google Package Manager compatable plugin. - This plugin demonstrates how a Unity plugin can be created with -the Google Package Manager Packager and be available from a Google -Package Manager registry. - - - \ No newline at end of file diff --git a/source/PackageManagerTests/testData/registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/gpm-example-plugin.unitypackage b/source/PackageManagerTests/testData/registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/gpm-example-plugin.unitypackage deleted file mode 100755 index b89606e1..00000000 Binary files a/source/PackageManagerTests/testData/registry/com.google.unity.example/gpm-example-plugin/1.0.0.0/gpm-example-plugin.unitypackage and /dev/null differ diff --git a/source/PackageManagerTests/testData/registry/com.google.unity.example/package-manifest.xml b/source/PackageManagerTests/testData/registry/com.google.unity.example/package-manifest.xml deleted file mode 100755 index a69e93e2..00000000 --- a/source/PackageManagerTests/testData/registry/com.google.unity.example/package-manifest.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - com.google.unity.example - gpm-example-plugin - 1.0.0.0 - unitypackage - - 1.0.0.0 - - 1.0.0.0 - - - 0 - \ No newline at end of file diff --git a/source/PackageManagerTests/testData/registry/registry.xml b/source/PackageManagerTests/testData/registry/registry.xml deleted file mode 100755 index f82f2ee5..00000000 --- a/source/PackageManagerTests/testData/registry/registry.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - registry.google.unity - jarresolver-google-registry - 0.0.1.1 - 1482171836 - - com.google.unity.example - - \ No newline at end of file diff --git a/source/PackageManagerTests/testData/registry2/com.google.unity.example2/gpm-example2-plugin/4.3.2.1/description.xml b/source/PackageManagerTests/testData/registry2/com.google.unity.example2/gpm-example2-plugin/4.3.2.1/description.xml deleted file mode 100755 index 9bfd8717..00000000 --- a/source/PackageManagerTests/testData/registry2/com.google.unity.example2/gpm-example2-plugin/4.3.2.1/description.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - Example2 Plugin - A demonstration of a Package Manager compatable plugin. - This plugin demonstrates how a Unity plugin can be created with -the Package Manager Packager and be available from a Google -Package Manager registry. - - - \ No newline at end of file diff --git a/source/PackageManagerTests/testData/registry2/com.google.unity.example2/gpm-example2-plugin/4.3.2.1/gpm-example2-plugin.unitypackage b/source/PackageManagerTests/testData/registry2/com.google.unity.example2/gpm-example2-plugin/4.3.2.1/gpm-example2-plugin.unitypackage deleted file mode 100755 index b89606e1..00000000 Binary files a/source/PackageManagerTests/testData/registry2/com.google.unity.example2/gpm-example2-plugin/4.3.2.1/gpm-example2-plugin.unitypackage and /dev/null differ diff --git a/source/PackageManagerTests/testData/registry2/com.google.unity.example2/package-manifest.xml b/source/PackageManagerTests/testData/registry2/com.google.unity.example2/package-manifest.xml deleted file mode 100755 index 3f5ccec1..00000000 --- a/source/PackageManagerTests/testData/registry2/com.google.unity.example2/package-manifest.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - com.google.unity.example2 - gpm-example2-plugin - 4.3.2.1 - unitypackage - - 4.3.2.1 - - 4.3.2.1 - - - 0 - \ No newline at end of file diff --git a/source/PackageManagerTests/testData/registry2/registry.xml b/source/PackageManagerTests/testData/registry2/registry.xml deleted file mode 100755 index 5704d477..00000000 --- a/source/PackageManagerTests/testData/registry2/registry.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - registry2.google.unity - test-sample-registry - 2.0.1.1 - 1482883127 - - com.google.unity.example2 - - \ No newline at end of file diff --git a/source/PlayServicesResolver/src/AlertModal.cs b/source/PlayServicesResolver/src/AlertModal.cs deleted file mode 100644 index b9bde4a3..00000000 --- a/source/PlayServicesResolver/src/AlertModal.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Runtime.Remoting.Messaging; -using UnityEngine; - -namespace GooglePlayServices { - using System; - using UnityEditor; - - /// - /// A fluid wrapper around the EditorUtility.DisplayDialogue - /// interface. - /// - public class AlertModal { - private const string DEFAULT_EMPTY = ""; - private const string DEFAULT_OK = "Yes"; - private const string DEFAULT_CANCEL = "No"; - private static Action DefaultEmptyAction = () => { }; - - public class LabeledAction { - public string Label { get; set; } - public Action DelegateAction { get; set; } - } - - /// - /// Add a title to your Dialog box - /// - public string Title { get; set; } - - /// - /// Add a message to your Dialog box - /// - public string Message { get; set; } - - /// - /// The text and action to associate with the "ok" button. - /// - public LabeledAction Ok { get; set; } - - /// - /// The text and action to associate with the "cancel" button. - /// - public LabeledAction Cancel { get; set; } - - /// - /// The text and action to associate with the "alt" button. - /// If this property is not specified, a two button display - /// will be used. - /// - public LabeledAction Alt { get; set; } - - /// - /// Constructor for the DialogBuilder sets defaults - /// for required fields. - /// - public AlertModal() { - Title = DEFAULT_EMPTY; - Message = DEFAULT_EMPTY; - Ok = new LabeledAction { - Label = DEFAULT_OK, - DelegateAction = DefaultEmptyAction - }; - Cancel = new LabeledAction { - Label = DEFAULT_CANCEL, - DelegateAction = DefaultEmptyAction - }; - } - - /// - /// Display the window for the user's input. If no "alt" button is - /// specified, display a normal DisplayDialog, otherwise use a - /// DisplayDialogComplex - /// - public void Display() { - if (Alt == null) { - DisplaySimple(); - } - else { - DisplayComplex(); - } - } - - /// - /// Display a ComplexDialog with title, message, - /// and 3 buttons - ok, cancel, and alt. - /// - private void DisplayComplex() { - int option = EditorUtility.DisplayDialogComplex(Title, Message, Ok.Label, - Cancel.Label, Alt.Label); - - switch (option) { - // Ok option (perform action in the affirmative) - case 0: - Ok.DelegateAction(); - break; - // Cancel option (whatever the negative is) - case 1: - Cancel.DelegateAction(); - break; - // Alt option (whatever the third option you intended is) - case 2: - Alt.DelegateAction(); - break; - } - } - - /// - /// Display a simple Dialog with a title, message, and - /// two buttons - ok and cancel. - /// - private void DisplaySimple() { - bool option = EditorUtility.DisplayDialog(Title, Message, Ok.Label, Cancel.Label); - - if (option) { - Ok.DelegateAction(); - } - else { - Cancel.DelegateAction(); - } - } - } -} \ No newline at end of file diff --git a/source/PlayServicesResolver/src/DefaultResolver.cs b/source/PlayServicesResolver/src/DefaultResolver.cs deleted file mode 100644 index c980d2eb..00000000 --- a/source/PlayServicesResolver/src/DefaultResolver.cs +++ /dev/null @@ -1,464 +0,0 @@ -// -// Copyright (C) 2015 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace GooglePlayServices -{ - using UnityEditor; - using Google; - using Google.JarResolver; - using System.IO; - using UnityEngine; - using System; - using System.Collections.Generic; - - /// - /// Default resolver base class. - /// - /// This class contains the default implementation of the - /// standard methods used to resolve the play-services dependencies. - /// The intention is that common, stable methods are implemented here, and - /// subsequent versions of the resolver would extend this class to modify the - /// behavior. - /// - public abstract class DefaultResolver : IResolver - { - // Namespace for resources under the src/scripts directory embedded within this assembly. - protected const string EMBEDDED_RESOURCES_NAMESPACE = "PlayServicesResolver.scripts."; - - #region IResolver implementation - - /// - /// Version of the resolver - 1.0.0 - /// - public virtual int Version() - { - return MakeVersionNumber(1, 0, 0); - } - - /// - /// Enables automatic resolution. - /// - /// If set to true flag. - public virtual void SetAutomaticResolutionEnabled(bool flag) - { - SettingsDialog.EnableAutoResolution = flag; - } - - /// - /// Returns true if automatic resolution is enabled. - /// Auto-resolution is never enabled in batch mode. Each build setting change must be - /// manually followed by DoResolution(). - /// - /// true, if resolution enabled was automaticed, false otherwise. - public virtual bool AutomaticResolutionEnabled() - { - return SettingsDialog.EnableAutoResolution && !ExecutionEnvironment.InBatchMode; - } - - /// - /// Returns true if Android package installation is enabled. - /// - /// true, package installation is enabled, false otherwise. - /// - public virtual bool AndroidPackageInstallationEnabled() - { - return SettingsDialog.InstallAndroidPackages; - } - - /// - /// Checks based on the asset changes, if resolution should occur. - /// - /// - /// The resolution only happens if a script file (.cs, or .js) was imported - /// or if an Android plugin was deleted. This allows for changes to - /// assets that do not affect the dependencies to happen without processing. - /// This also avoids an infinite loop when a version of a dependency is - /// deleted during resolution. - /// - /// true, if auto resolution should happen, false otherwise. - /// Imported assets. - /// Deleted assets. - /// Moved assets. - /// Moved from asset paths. - [Obsolete] - public virtual bool ShouldAutoResolve( - string[] importedAssets, - string[] deletedAssets, - string[] movedAssets, - string[] movedFromAssetPaths) { return false; } - - /// - /// Shows the settings dialog. - /// - public virtual void ShowSettingsDialog() { ShowSettings(); } - - /// - /// Show the settings dialog. - /// This method is used when a Resolver isn't instanced. - /// - internal static void ShowSettings() - { - SettingsDialog window = (SettingsDialog)EditorWindow.GetWindow( - typeof(SettingsDialog), true, "Android Resolver Settings"); - window.Initialize(); - window.Show(); - } - - /// - /// Does the resolution of the play-services aars. - /// - /// Svc support. - /// Destination directory. - /// Delegate called when resolution is complete. - public virtual void DoResolution(PlayServicesSupport svcSupport, - string destinationDirectory, - System.Action resolutionComplete) - { - resolutionComplete(); - } - - /// - /// Called during Update to allow the resolver to check the bundle ID of the application - /// to see whether resolution should be triggered again. - /// - /// - /// - /// Array of packages that should be re-resolved if resolution should occur, - /// null otherwise. - [Obsolete] - public virtual string[] OnBundleId(string bundleId) { return OnBuildSettings(); } - - /// - /// Called during Update to allow the resolver to check any build settings of managed - /// packages to see whether resolution should be triggered again. - /// - /// Array of packages that should be re-resolved if resolution should occur, - /// null otherwise. - public virtual string[] OnBuildSettings() { return null; } - - #endregion - - /// - /// Compatibility method for synchronous implementations of DoResolution(). - /// - /// Svc support. - /// Destination directory. - public virtual void DoResolution(PlayServicesSupport svcSupport, - string destinationDirectory) - { - DoResolution(svcSupport, destinationDirectory, () => {}); - } - - /// - /// Makes the version number. - /// - /// This combines the major/minor/point version components into - /// an integer. If multiple resolvers are registered, then the greatest version - /// is used. - /// - /// The version number. - /// Maj. - /// Minimum. - /// Point. - internal int MakeVersionNumber(int maj, int min, int pt) - { - return maj * 10000 + min + 100 + pt; - } - - /// - /// Gets a value indicating whether this version of Unity supports aar files. - /// - /// true if supports aar files; otherwise, false. - internal bool SupportsAarFiles - { - get - { - // Get the version number. - string majorVersion = Application.unityVersion.Split('.')[0]; - int ver; - if (!int.TryParse(majorVersion, out ver)) - { - ver = 4; - } - return ver >= 5; - } - } - - /// - /// Create an AAR from the specified directory. - /// - /// AAR file to create. - /// Directory which contains the set of files to store - /// in the AAR. - /// true if successful, false otherwise. - internal virtual bool ArchiveAar(string aarFile, string inputDirectory) { - try { - string aarPath = Path.GetFullPath(aarFile); - CommandLine.Result result = CommandLine.Run( - JavaUtilities.JarBinaryPath, - String.Format("cvf{0} \"{1}\" -C \"{2}\" .", - aarFile.ToLower().EndsWith(".jar") ? "" : "M", aarPath, - inputDirectory)); - if (result.exitCode != 0) { - Debug.LogError(String.Format("Error archiving {0}\n" + - "Exit code: {1}\n" + - "{2}\n" + - "{3}\n", - aarPath, result.exitCode, result.stdout, - result.stderr)); - return false; - } - } catch (Exception e) { - Debug.LogError(e); - throw e; - } - return true; - } - - // Native library ABI subdirectories supported by Unity. - // Directories that contain native libraries within a Unity Android library project. - private static string[] NATIVE_LIBRARY_DIRECTORIES = new string[] { "libs", "jni" }; - - /// - /// Get the set of native library ABIs in an exploded AAR. - /// - /// Directory to search for ABIs. - /// Set of ABI directory names in the exploded AAR or null if none are - /// found. - internal AndroidAbis AarDirectoryFindAbis(string aarDirectory) { - var foundAbis = new HashSet(); - foreach (var libDirectory in NATIVE_LIBRARY_DIRECTORIES) { - foreach (var abiDir in AndroidAbis.AllSupported) { - if (Directory.Exists(Path.Combine(aarDirectory, - Path.Combine(libDirectory, abiDir)))) { - foundAbis.Add(abiDir); - } - } - } - return foundAbis.Count > 0 ? new AndroidAbis(foundAbis) : null; - } - - /// - /// Explodes a single aar file. This is done by calling the - /// JDK "jar" command, then moving the classes.jar file. - /// - /// The directory to unpack / explode the AAR to. If antProject is true - /// the ant project will be located in Path.Combine(dir, Path.GetFileName(aarFile)). - /// Aar file to explode. - /// true to explode into an Ant style project or false - /// to repack the processed AAR as a new AAR. - /// ABIs in the AAR or null if it's universal. - /// true if successful, false otherwise. - internal bool ProcessAar(string dir, string aarFile, bool antProject, - out AndroidAbis abis) { - PlayServicesResolver.Log(String.Format("ProcessAar {0} {1} antProject={2}", - dir, aarFile, antProject), - level: LogLevel.Verbose); - abis = null; - string aarDirName = Path.GetFileNameWithoutExtension(aarFile); - // Output directory for the contents of the AAR / JAR. - string outputDir = Path.Combine(dir, aarDirName); - string stagingDir = FileUtils.CreateTemporaryDirectory(); - if (stagingDir == null) { - PlayServicesResolver.Log(String.Format( - "Unable to create temporary directory to process AAR {0}", aarFile), - level: LogLevel.Error); - return false; - } - try { - string workingDir = Path.Combine(stagingDir, aarDirName); - var deleteError = FileUtils.FormatError( - String.Format("Failed to create working directory to process AAR {0}", - aarFile), FileUtils.DeleteExistingFileOrDirectory(workingDir)); - if (!String.IsNullOrEmpty(deleteError)) { - PlayServicesResolver.Log(deleteError, level: LogLevel.Error); - return false; - } - Directory.CreateDirectory(workingDir); - if (!PlayServicesResolver.ExtractZip(aarFile, null, workingDir)) return false; - PlayServicesResolver.ReplaceVariablesInAndroidManifest( - Path.Combine(workingDir, "AndroidManifest.xml"), - PlayServicesResolver.GetAndroidApplicationId(), - new Dictionary()); - - string nativeLibsDir = null; - if (antProject) { - // Create the libs directory to store the classes.jar and non-Java shared - // libraries. - string libDir = Path.Combine(workingDir, "libs"); - nativeLibsDir = libDir; - Directory.CreateDirectory(libDir); - - // Move the classes.jar file to libs. - string classesFile = Path.Combine(workingDir, "classes.jar"); - string targetClassesFile = Path.Combine(libDir, Path.GetFileName(classesFile)); - if (File.Exists(targetClassesFile)) File.Delete(targetClassesFile); - if (File.Exists(classesFile)) { - FileUtils.MoveFile(classesFile, targetClassesFile); - } else { - // Some libraries publish AARs that are poorly formatted (e.g missing - // a classes.jar file). Firebase's license AARs at certain versions are - // examples of this. When Unity's internal build system detects an Ant - // project or AAR without a classes.jar, the build is aborted. This - // generates an empty classes.jar file to workaround the issue. - string emptyClassesDir = Path.Combine(stagingDir, "empty_classes_jar"); - Directory.CreateDirectory(emptyClassesDir); - if (!ArchiveAar(targetClassesFile, emptyClassesDir)) return false; - } - } - - // Copy non-Java shared libraries (.so) files from the "jni" directory into the - // lib directory so that Unity's legacy (Ant-like) build system includes them in the - // built APK. - string jniLibDir = Path.Combine(workingDir, "jni"); - nativeLibsDir = nativeLibsDir ?? jniLibDir; - if (Directory.Exists(jniLibDir)) { - var abisInArchive = AarDirectoryFindAbis(workingDir); - if (jniLibDir != nativeLibsDir) { - FileUtils.CopyDirectory(jniLibDir, nativeLibsDir); - deleteError = FileUtils.FormatError( - String.Format("Unable to delete JNI directory from AAR {0}", aarFile), - FileUtils.DeleteExistingFileOrDirectory(jniLibDir)); - if (!String.IsNullOrEmpty(deleteError)) { - PlayServicesResolver.Log(deleteError, level: LogLevel.Error); - return false; - } - } - if (abisInArchive != null) { - // Remove shared libraries for all ABIs that are not required for the - // selected ABIs. - var activeAbisSet = AndroidAbis.Current.ToSet(); - var abisInArchiveSet = abisInArchive.ToSet(); - var abisInArchiveToRemoveSet = new HashSet(abisInArchiveSet); - abisInArchiveToRemoveSet.ExceptWith(activeAbisSet); - - Func, string> setToString = (setToConvert) => { - return String.Join(", ", (new List(setToConvert)).ToArray()); - }; - PlayServicesResolver.Log( - String.Format( - "Target ABIs [{0}], ABIs [{1}] in {2}, will remove [{3}] ABIs", - setToString(activeAbisSet), - setToString(abisInArchiveSet), - aarFile, - setToString(abisInArchiveToRemoveSet)), - level: LogLevel.Verbose); - - foreach (var abiToRemove in abisInArchiveToRemoveSet) { - abisInArchiveSet.Remove(abiToRemove); - deleteError = FileUtils.FormatError( - String.Format("Unable to remove unused ABIs from {0}", aarFile), - FileUtils.DeleteExistingFileOrDirectory( - Path.Combine(nativeLibsDir, abiToRemove))); - if (!String.IsNullOrEmpty(deleteError)) { - PlayServicesResolver.Log(deleteError, LogLevel.Warning); - } - } - abis = new AndroidAbis(abisInArchiveSet); - } - } - - if (antProject) { - // Create the project.properties file which indicates to Unity that this - // directory is a plugin. - string projectProperties = Path.Combine(workingDir, "project.properties"); - if (!File.Exists(projectProperties)) { - File.WriteAllLines(projectProperties, new [] { - "# Project target.", - "target=android-9", - "android.library=true" - }); - } - PlayServicesResolver.Log( - String.Format("Creating Ant project: Replacing {0} with {1}", aarFile, - outputDir), level: LogLevel.Verbose); - // Clean up the aar file. - deleteError = FileUtils.FormatError( - String.Format("Failed to clean up AAR file {0} after generating " + - "Ant project {1}", aarFile, outputDir), - FileUtils.DeleteExistingFileOrDirectory(Path.GetFullPath(aarFile))); - if (!String.IsNullOrEmpty(deleteError)) { - PlayServicesResolver.Log(deleteError, level: LogLevel.Error); - return false; - } - // Create the output directory. - FileUtils.MoveDirectory(workingDir, outputDir); - // Add a tracking label to the exploded files. - PlayServicesResolver.LabelAssets(new [] { outputDir }); - } else { - // Add a tracking label to the exploded files just in-case packaging fails. - PlayServicesResolver.Log(String.Format("Repacking {0} from {1}", - aarFile, workingDir), - level: LogLevel.Verbose); - // Create a new AAR file. - deleteError = FileUtils.FormatError( - String.Format("Failed to replace AAR file {0}", aarFile), - FileUtils.DeleteExistingFileOrDirectory(Path.GetFullPath(aarFile))); - if (!String.IsNullOrEmpty(deleteError)) { - PlayServicesResolver.Log(deleteError, level: LogLevel.Error); - return false; - } - if (!ArchiveAar(aarFile, workingDir)) return false; - PlayServicesResolver.LabelAssets(new [] { aarFile }); - } - } catch (Exception e) { - PlayServicesResolver.Log(String.Format("Failed to process AAR {0} ({1}", - aarFile, e), - level: LogLevel.Error); - } finally { - // Clean up the temporary directory. - var deleteError = FileUtils.FormatError( - String.Format("Failed to clean up temporary folder while processing {0}", - aarFile), FileUtils.DeleteExistingFileOrDirectory(stagingDir)); - if (!String.IsNullOrEmpty(deleteError)) { - PlayServicesResolver.Log(deleteError, level: LogLevel.Warning); - } - } - return true; - } - - /// - /// Extract a list of embedded resources to the specified path creating intermediate - /// directories if they're required. - /// - /// Each Key is the resource to extract and each - /// Value is the path to extract to. - protected static void ExtractResources(List> - resourceNameToTargetPaths) { - foreach (var kv in resourceNameToTargetPaths) ExtractResource(kv.Key, kv.Value); - } - - /// - /// Extract an embedded resource to the specified path creating intermediate directories - /// if they're required. - /// - /// Name of the resource to extract. - /// Target path. - protected static void ExtractResource(string resourceName, string targetPath) { - Directory.CreateDirectory(Path.GetDirectoryName(targetPath)); - var stream = typeof(GooglePlayServices.ResolverVer1_1).Assembly. - GetManifestResourceStream(resourceName); - if (stream == null) { - UnityEngine.Debug.LogError(String.Format("Failed to find resource {0} in assembly", - resourceName)); - return; - } - var data = new byte[stream.Length]; - stream.Read(data, 0, (int)stream.Length); - File.WriteAllBytes(targetPath, data); - } - } -} diff --git a/source/PlayServicesResolver/src/GradleTemplateResolver.cs b/source/PlayServicesResolver/src/GradleTemplateResolver.cs deleted file mode 100644 index e275762f..00000000 --- a/source/PlayServicesResolver/src/GradleTemplateResolver.cs +++ /dev/null @@ -1,305 +0,0 @@ -// -// Copyright (C) 2019 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace GooglePlayServices { - using System; - using System.Collections.Generic; - using System.IO; - - using Google; - using Google.JarResolver; - - using UnityEditor; - - /// - /// Resolver which simply injects dependencies into a gradle template file. - /// - internal class GradleTemplateResolver { - - /// - /// Path of the Gradle template file. - /// - public static string GradleTemplatePath = - Path.Combine(SettingsDialog.AndroidPluginsDir, "mainTemplate.gradle"); - - /// - /// Line that indicates the start of the injected repos block in the template. - /// - private const string ReposStartLine = "// Android Resolver Repos Start"; - - /// - /// Line that indicates the end of the injected repos block in the template. - /// - private const string ReposEndLine = "// Android Resolver Repos End"; - - /// - /// Line that indicates where to initially inject repos in the default template. - /// - private const string ReposInjectionLine = "apply plugin: 'com.android.application'"; - - /// - /// Line that indicates the start of the injected dependencies block in the template. - /// - private const string DependenciesStartLine = "// Android Resolver Dependencies Start"; - - /// - /// Line that indicates the end of the injected dependencies block in the template. - /// - private const string DependenciesEndLine = "// Android Resolver Dependencies End"; - - /// - /// Token that indicates where dependencies should initially be injected. - /// If this isn't present in the template dependencies will not be injected or they'll - /// be removed. - /// - private const string DependenciesToken = "**DEPS**"; - - - /// - /// Copy srcaar files to aar files that are excluded from Unity's build process. - /// - /// Dependencies to inject. - /// true if successful, false otherwise. - private static bool CopySrcAars(ICollection dependencies) { - bool succeeded = true; - var aarFiles = new List(); - // Copy each .srcaar file to .aar while configuring the plugin importer to ignore the - // file. - foreach (var aar in LocalMavenRepository.FindAarsInLocalRepos(dependencies)) { - var dir = Path.GetDirectoryName(aar); - var filename = Path.GetFileNameWithoutExtension(aar); - var targetFilename = Path.Combine(dir, filename + ".aar"); - bool configuredAar = File.Exists(targetFilename); - if (!configuredAar) { - bool copiedAndLabeledAar = AssetDatabase.CopyAsset(aar, targetFilename); - if (copiedAndLabeledAar) { - var unlabeledAssets = new HashSet(); - PlayServicesResolver.LabelAssets( - new [] { targetFilename }, - complete: (unlabeled) => { unlabeledAssets.UnionWith(unlabeled); }); - copiedAndLabeledAar = unlabeledAssets.Count == 0; - } - if (copiedAndLabeledAar) { - try { - PluginImporter importer = (PluginImporter)AssetImporter.GetAtPath( - targetFilename); - importer.SetCompatibleWithAnyPlatform(false); - importer.SetCompatibleWithPlatform(BuildTarget.Android, false); - configuredAar = true; - } catch (Exception ex) { - PlayServicesResolver.Log(String.Format( - "Failed to disable {0} from being included by Unity's " + - "internal build. {0} has been deleted and will not be " + - "included in Gradle builds. ({1})", aar, ex), - level: LogLevel.Error); - } - } else { - PlayServicesResolver.Log(String.Format( - "Unable to copy {0} to {1}. {1} will not be included in Gradle " + - "builds.", aar, targetFilename), level: LogLevel.Error); - } - if (configuredAar) { - aarFiles.Add(targetFilename); - } else { - if (File.Exists(targetFilename)) { - AssetDatabase.DeleteAsset(targetFilename); - } - succeeded = false; - } - } - } - foreach (var aar in aarFiles) { - if (!LocalMavenRepository.PatchPomFile(aar)) succeeded = false; - } - return succeeded; - } - - /// - /// Finds an area in a set of lines to inject a block of text. - /// - private class TextFileLineInjector { - // Token which, if found within a line, indicates where to inject the block of text if - // the start / end block isn't found - private string injectionToken; - // Line that indicates the start of the block to replace. - private string startBlockLine; - // Line that indicates the end of the block to replace. - private string endBlockLine; - // Lines to inject. - private List replacementLines; - // Shorthand name of the replacement block. - private string replacementName; - // Description of the file being modified. - private string fileDescription; - // Whether replacementLines has been injected. - private bool injected = false; - // Whether the injector is tracking a line between startBlockLine and endBlockLine. - private bool inBlock = false; - - /// - /// Construct the injector. - /// - /// Token which, if found within a line, indicates where - /// to inject the block of text if the start / end block isn't found. - /// Line which indicates the start of the block to - /// replace. - /// Line which indicates the end of the block to replace. - /// - /// Lines to inject. - /// Shorthand name of the replacement block. - /// Description of the file being modified. - public TextFileLineInjector(string injectionToken, - string startBlockLine, - string endBlockLine, - ICollection replacementLines, - string replacementName, - string fileDescription) { - this.injectionToken = injectionToken; - this.startBlockLine = startBlockLine; - this.endBlockLine = endBlockLine; - this.replacementLines = new List(); - if (replacementLines.Count > 0) { - this.replacementLines.Add(startBlockLine); - this.replacementLines.AddRange(replacementLines); - this.replacementLines.Add(endBlockLine); - } - this.replacementName = replacementName; - this.fileDescription = fileDescription; - } - - /// - /// Process a line returning the set of lines to emit for this line. - /// - /// Line to process. - /// Whether lines were injected. - /// List of lines to emit for the specified line. - public List ProcessLine(string line, out bool injectionApplied) { - var trimmedLine = line.Trim(); - var outputLines = new List { line }; - bool injectBlock = false; - injectionApplied = false; - if (injected) { - return outputLines; - } - if (!inBlock) { - if (trimmedLine.StartsWith(startBlockLine)) { - inBlock = true; - outputLines.Clear(); - } else if (trimmedLine.Contains(injectionToken)) { - injectBlock = true; - } - } else { - outputLines.Clear(); - if (trimmedLine.StartsWith(endBlockLine)) { - inBlock = false; - injectBlock = true; - } - } - if (injectBlock) { - injected = true; - injectionApplied = true; - if (replacementLines.Count > 0) { - PlayServicesResolver.Log(String.Format("Adding {0} to {1}", - replacementName, fileDescription), - level: LogLevel.Verbose); - outputLines.InsertRange(0, replacementLines); - } - } - return outputLines; - } - } - - /// - /// Inject / update dependencies in the gradle template file. - /// - /// Dependencies to inject. - /// true if successful, false otherwise. - public static bool InjectDependencies(ICollection dependencies) { - var fileDescription = String.Format("gradle template {0}", GradleTemplatePath); - PlayServicesResolver.Log(String.Format("Reading {0}", fileDescription), - level: LogLevel.Verbose); - IEnumerable lines; - try { - lines = File.ReadAllLines(GradleTemplatePath); - } catch (Exception ex) { - PlayServicesResolver.Log( - String.Format("Unable to patch {0} ({1})", fileDescription, ex.ToString()), - level: LogLevel.Error); - return false; - } - - PlayServicesResolver.Log(String.Format("Searching for {0} in {1}", DependenciesToken, - fileDescription), - level: LogLevel.Verbose); - // Determine whether dependencies should be injected. - bool containsDeps = false; - foreach (var line in lines) { - if (line.Contains(DependenciesToken)) { - containsDeps = true; - break; - } - } - - // If a dependencies token isn't present report a warning and abort. - if (!containsDeps) { - PlayServicesResolver.Log( - String.Format("No {0} token found in {1}, Android Resolver libraries will " + - "not be added to the file.", DependenciesToken, fileDescription), - level: LogLevel.Warning); - return true; - } - - // Copy all srcaar files in the project to aar filenames so that they'll be included in - // the Gradle build. - if (!CopySrcAars(dependencies)) return false; - - TextFileLineInjector[] injectors = new [] { - new TextFileLineInjector(ReposInjectionLine, ReposStartLine, ReposEndLine, - PlayServicesResolver.GradleMavenReposLines(dependencies), - "Repos", fileDescription), - new TextFileLineInjector(DependenciesToken, DependenciesStartLine, - DependenciesEndLine, - PlayServicesResolver.GradleDependenciesLines( - dependencies, includeDependenciesBlock: false), - "Dependencies", fileDescription) - }; - // Lines that will be written to the output file. - var outputLines = new List(); - foreach (var line in lines) { - var currentOutputLines = new List(); - foreach (var injector in injectors) { - bool injectionApplied = false; - currentOutputLines = injector.ProcessLine(line, out injectionApplied); - if (injectionApplied || currentOutputLines.Count == 0) break; - } - outputLines.AddRange(currentOutputLines); - } - PlayServicesResolver.Log( - String.Format("Writing updated {0}", fileDescription), - level: LogLevel.Verbose); - try { - File.WriteAllText(GradleTemplatePath, - String.Join("\n", outputLines.ToArray()) + "\n"); - } catch (Exception ex) { - PlayServicesResolver.Log( - String.Format("Unable to patch {0} ({1})", fileDescription, - ex.ToString()), level: LogLevel.Error); - return false; - } - return true; - } - } -} diff --git a/source/PlayServicesResolver/src/IResolver.cs b/source/PlayServicesResolver/src/IResolver.cs deleted file mode 100644 index ade78ce4..00000000 --- a/source/PlayServicesResolver/src/IResolver.cs +++ /dev/null @@ -1,107 +0,0 @@ -// -// Copyright (C) 2015 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace GooglePlayServices -{ - using Google.JarResolver; - using System; - - public interface IResolver - { - /// - /// Version of the resolver. - /// - /// - /// The resolver with the greatest version is used when resolving. - /// The value of the version is calculated using MakeVersion in DefaultResolver - /// - /// - int Version(); - - /// - /// Returns true if automatic resolution is enabled. - /// Auto-resolution is never enabled in batch mode. Each build setting change must be - /// manually followed by DoResolution(). - /// - /// true, if resolution is enabled false otherwise. - bool AutomaticResolutionEnabled(); - - /// - /// Sets the automatic resolution flag. - /// - /// If set to true flag. - void SetAutomaticResolutionEnabled(bool flag); - - /// - /// Checks based on the asset changes, if resolution should occur. - /// - /// - /// Resolution should only happen when needed, and avoid infinite loops - /// of automatic resolution triggered by resolution actions. - /// - /// true, if auto resolution should happen, false otherwise. - /// - /// Imported assets. - /// Deleted assets. - /// Moved assets. - /// Moved from asset paths. - [Obsolete] - bool ShouldAutoResolve(string[] importedAssets, - string[] deletedAssets, - string[] movedAssets, - string[] movedFromAssetPaths); - - /// - /// Shows the settings dialog. - /// - void ShowSettingsDialog(); - - /// - /// Does the resolution of the play-services aars. - /// - /// Svc support. - /// Destination directory. - /// Delegate called when resolution is complete. - void DoResolution(PlayServicesSupport svcSupport, - string destinationDirectory, - System.Action resolutionComplete); - - /// - /// Does the resolution of the play-services aars. - /// - /// Svc support. - /// Destination directory. - void DoResolution(PlayServicesSupport svcSupport, - string destinationDirectory); - - /// - /// Called during Update to allow the resolver to check the bundle ID of the application - /// to see whether resolution should be triggered again. - /// - /// Array of packages that should be re-resolved if resolution should occur, - /// null otherwise. - [Obsolete] - string[] OnBundleId(string bundleId); - - /// Called during Update to allow the resolver to check any build settings of managed - /// packages to see whether resolution should be triggered again. - /// - /// Array of packages that should be re-resolved if resolution should occur, - /// null otherwise. - string[] OnBuildSettings(); - } -} - diff --git a/source/PlayServicesResolver/src/ResolverVer1_1.cs b/source/PlayServicesResolver/src/ResolverVer1_1.cs deleted file mode 100644 index 602862ab..00000000 --- a/source/PlayServicesResolver/src/ResolverVer1_1.cs +++ /dev/null @@ -1,1718 +0,0 @@ -// -// Copyright (C) 2015 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace GooglePlayServices -{ - using UnityEngine; - using UnityEditor; - using System.Collections.Generic; - using System.Collections.Specialized; - using Google; - using Google.JarResolver; - using System; - using System.Collections; - using System.IO; - using System.Text.RegularExpressions; - using System.Xml; - - public class ResolverVer1_1 : DefaultResolver - { - // Caches data associated with an aar so that it doesn't need to be queried to determine - // whether it should be expanded / exploded if it hasn't changed. - private class AarExplodeData - { - // Identifier for an ABI independent AAR. - public const string ABI_UNIVERSAL = "universal"; - // Time the file was modified the last time it was inspected. - public System.DateTime modificationTime; - // Whether the AAR file should be expanded / exploded. - public bool explode = false; - // Project's bundle ID when this was expanded. - public string bundleId = ""; - // Path of the target AAR package. - public string path = ""; - // Comma separated string that lists the set of *available* ABIs in the source archive. - // This is ABI_UNIVERSAL if the archive does not contain any native libraries. - public string availableAbis = ABI_UNIVERSAL; - // Comma separated string that lists the set of ABIs in the archive. - // This is ABI_UNIVERSAL if the archive does not contain any native libraries. - public string targetAbis = ABI_UNIVERSAL; - // Whether gradle is selected as the build system. - public bool gradleBuildSystem = PlayServicesResolver.GradleBuildEnabled; - // Whether gradle export is enabled. - public bool gradleExport = PlayServicesResolver.GradleProjectExportEnabled; - // Whether the gradle template is enabled. - public bool gradleTemplate = PlayServicesResolver.GradleTemplateEnabled; - // AAR version that should be ignored when attempting to overwrite an existing - // dependency. This is reset when the dependency is updated to a version different - // to this. - // NOTE: This is not considered in AarExplodeDataIsDirty() as we do not want to - // re-explode an AAR if this changes. - public string ignoredVersion = ""; - - /// - /// Convert a comma separated list of ABIs to an AndroidAbis instance. - /// - /// String to convert. - /// AndroidAbis instance if native components are present, - /// null otherwise. - private static AndroidAbis AndroidAbisFromString(string abiString) { - return abiString == ABI_UNIVERSAL ? null : new AndroidAbis(abiString); - } - - /// - /// Convert an AndroidAbis instance to a comma separated string. - /// - /// Instance to convert. - /// Comma separated string. - private static string AndroidAbisToString(AndroidAbis abis) { - return abis != null ? abis.ToString() : ABI_UNIVERSAL; - } - - /// - /// Get the available native component ABIs in the archive. - /// If this is a universal archive it returns null. - /// - public AndroidAbis AvailableAbis { - get { return AndroidAbisFromString(availableAbis); } - set { availableAbis = AndroidAbisToString(value); } - } - - /// - /// Get the current native component ABIs in the archive. - /// If this is a universal archive it returns null. - /// - public AndroidAbis TargetAbis { - get { return AndroidAbisFromString(targetAbis); } - set { targetAbis = AndroidAbisToString(value); } - } - - /// - /// Default constructor. - /// - public AarExplodeData() {} - - /// - /// Copy an instance of this object. - /// - public AarExplodeData(AarExplodeData dataToCopy) { - modificationTime = dataToCopy.modificationTime; - explode = dataToCopy.explode; - bundleId = dataToCopy.bundleId; - path = dataToCopy.path; - targetAbis = dataToCopy.targetAbis; - gradleBuildSystem = dataToCopy.gradleBuildSystem; - gradleExport = dataToCopy.gradleExport; - gradleTemplate = dataToCopy.gradleTemplate; - ignoredVersion = dataToCopy.ignoredVersion; - } - - /// - /// Compare with this object. - /// - /// Object to compare with. - /// true if both objects have the same contents, false otherwise. - public override bool Equals(System.Object obj) { - var data = obj as AarExplodeData; - return data != null && - modificationTime == data.modificationTime && - explode == data.explode && - bundleId == data.bundleId && - path == data.path && - availableAbis == data.availableAbis && - targetAbis == data.targetAbis && - gradleBuildSystem == data.gradleBuildSystem && - gradleExport == data.gradleExport && - gradleTemplate == data.gradleTemplate && - ignoredVersion == data.ignoredVersion; - } - - /// - /// Generate a hash of this object. - /// - /// - public override int GetHashCode() { - return modificationTime.GetHashCode() ^ - explode.GetHashCode() ^ - bundleId.GetHashCode() ^ - path.GetHashCode() ^ - availableAbis.GetHashCode() ^ - targetAbis.GetHashCode() ^ - gradleBuildSystem.GetHashCode() ^ - gradleExport.GetHashCode() ^ - gradleTemplate.GetHashCode() ^ - ignoredVersion.GetHashCode(); - } - - /// - /// Copy AAR explode data. - /// - public static Dictionary CopyDictionary( - Dictionary dataToCopy) { - var copy = new Dictionary(); - foreach (var item in dataToCopy) { - copy[item.Key] = new AarExplodeData(item.Value); - } - return copy; - } - - /// - /// Convert AAR data to a string. - /// - /// String description of the instance. - public override string ToString() { - return String.Format("modificationTime={0} " + - "explode={1} " + - "bundleId={2} " + - "path={3} " + - "availableAbis={4} " + - "targetAbis={5} " + - "gradleBuildSystem={6} " + - "gradleExport={7} " + - "gradleTemplate={8}", - modificationTime, explode, bundleId, path, availableAbis, - targetAbis, gradleBuildSystem, gradleExport, gradleTemplate); - } - } - - // Data that should be stored in the explode cache. - private Dictionary aarExplodeData = - new Dictionary(); - // Data currently stored in the explode cache. - private Dictionary aarExplodeDataSaved = - new Dictionary(); - // File used to to serialize aarExplodeData. This is required as Unity will reload classes - // in the editor when C# files are modified. - private string aarExplodeDataFile = Path.Combine(FileUtils.ProjectTemporaryDirectory, - "GoogleAarExplodeCache.xml"); - - // Directory used to execute Gradle. - private string gradleBuildDirectory = Path.Combine(FileUtils.ProjectTemporaryDirectory, - "PlayServicesResolverGradle"); - - private const int MajorVersion = 1; - private const int MinorVersion = 1; - private const int PointVersion = 0; - - // Characters that are parsed by Gradle / Java in property values. - // These characters need to be escaped to be correctly interpreted in a property value. - private static string[] GradlePropertySpecialCharacters = new string[] { - " ", "\\", "#", "!", "=", ":" - }; - - // Special characters that should not be escaped in URIs for Gradle property values. - private static HashSet GradleUriExcludeEscapeCharacters = new HashSet { - ":" - }; - - // Queue of System.Action objects for resolve actions to execute on the main thread. - private static System.Collections.Queue resolveUpdateQueue = new System.Collections.Queue(); - // Currently active resolution operation. - private static System.Action resolveActionActive = null; - // Lock for resolveUpdateQueue and resolveActionActive. - private static object resolveLock = new object(); - - public ResolverVer1_1() { - RunOnMainThread.Run(LoadAarExplodeCache, false); - } - - /// - /// Compare two dictionaries of AarExplodeData. - /// - private bool CompareExplodeData(Dictionary explodeData1, - Dictionary explodeData2) { - if (explodeData1 == explodeData2) return true; - if (explodeData1 == null || explodeData2 == null) return false; - if (explodeData1.Count != explodeData2.Count) return false; - var keys = new HashSet(explodeData1.Keys); - keys.UnionWith(new HashSet(explodeData2.Keys)); - foreach (var key in keys) { - AarExplodeData data1; - AarExplodeData data2; - if (!(explodeData1.TryGetValue(key, out data1) && - explodeData2.TryGetValue(key, out data2))) { - return false; - } - if (!data1.Equals(data2)) { - return false; - } - } - return true; - } - - /// - /// Load data cached in aarExplodeDataFile into aarExplodeData. - /// - private void LoadAarExplodeCache() { - if (!File.Exists(aarExplodeDataFile)) { - // Build aarExplodeData from the current set of AARs in the project. - foreach (string path in PlayServicesResolver.FindLabeledAssets()) { - PlayServicesResolver.Log(String.Format("Caching AAR {0} state", - path), LogLevel.Verbose); - ShouldExplode(path); - } - return; - } - - try { - using (XmlTextReader reader = - new XmlTextReader(new StreamReader(aarExplodeDataFile))) { - aarExplodeData.Clear(); - while (reader.Read()) { - if (reader.NodeType == XmlNodeType.Element && reader.Name == "aars") { - while (reader.Read()) { - if (reader.NodeType == XmlNodeType.Element && - reader.Name == "explodeData") { - string aar = ""; - AarExplodeData aarData = new AarExplodeData(); - do { - if (!reader.Read()) break; - - if (reader.NodeType == XmlNodeType.Element) { - string elementName = reader.Name; - if (reader.Read() && - reader.NodeType == XmlNodeType.Text) { - if (elementName == "aar") { - aar = reader.ReadContentAsString(); - } else if (elementName == "modificationTime") { - aarData.modificationTime = - reader.ReadContentAsDateTime(); - } else if (elementName == "explode") { - aarData.explode = reader.ReadContentAsBoolean(); - } else if (elementName == "bundleId") { - aarData.bundleId = reader.ReadContentAsString(); - } else if (elementName == "path") { - aarData.path = reader.ReadContentAsString(); - } else if (elementName == "availableAbis") { - aarData.availableAbis = - reader.ReadContentAsString(); - } else if (elementName == "targetAbi") { - aarData.targetAbis = - reader.ReadContentAsString(); - } else if (elementName == "gradleBuildSystem") { - aarData.gradleBuildSystem = - reader.ReadContentAsBoolean(); - } else if (elementName == "gradleExport") { - aarData.gradleExport = - reader.ReadContentAsBoolean(); - } else if (elementName == "gradleTemplate") { - aarData.gradleTemplate = - reader.ReadContentAsBoolean(); - } else if (elementName == "ignoredVersion") { - aarData.ignoredVersion = - reader.ReadContentAsString(); - } - } - } - } while (!(reader.Name == "explodeData" && - reader.NodeType == XmlNodeType.EndElement)); - if (aar != "" && aarData.path != "") { - aarExplodeData[aar] = aarData; - } - } - } - } - } - reader.Close(); - } - } catch (Exception e) { - PlayServicesResolver.Log(String.Format( - "Failed to read AAR cache {0} ({1})\n" + - "Auto-resolution will be slower.\n", aarExplodeDataFile, e.ToString()), - level: LogLevel.Warning); - } - aarExplodeDataSaved = AarExplodeData.CopyDictionary(aarExplodeData); - } - - /// - /// Save data from aarExplodeData into aarExplodeDataFile. - /// - private void SaveAarExplodeCache() - { - try { - if (File.Exists(aarExplodeDataFile)) - { - // If the explode data hasn't been modified, don't save. - if (CompareExplodeData(aarExplodeData, aarExplodeDataSaved)) return; - File.Delete(aarExplodeDataFile); - } - using (XmlTextWriter writer = - new XmlTextWriter(new StreamWriter(aarExplodeDataFile)) { - Formatting = Formatting.Indented - }) { - writer.WriteStartElement("aars"); - foreach (KeyValuePair kv in aarExplodeData) - { - writer.WriteStartElement("explodeData"); - writer.WriteStartElement("aar"); - writer.WriteValue(kv.Key); - writer.WriteEndElement(); - writer.WriteStartElement("modificationTime"); - writer.WriteValue(kv.Value.modificationTime); - writer.WriteEndElement(); - writer.WriteStartElement("explode"); - writer.WriteValue(kv.Value.explode); - writer.WriteEndElement(); - writer.WriteStartElement("bundleId"); - writer.WriteValue(PlayServicesResolver.GetAndroidApplicationId()); - writer.WriteEndElement(); - writer.WriteStartElement("path"); - writer.WriteValue(kv.Value.path); - writer.WriteEndElement(); - writer.WriteStartElement("availableAbis"); - writer.WriteValue(kv.Value.availableAbis); - writer.WriteEndElement(); - writer.WriteStartElement("targetAbi"); - writer.WriteValue(kv.Value.targetAbis); - writer.WriteEndElement(); - writer.WriteStartElement("gradleBuildEnabled"); - writer.WriteValue(kv.Value.gradleBuildSystem); - writer.WriteEndElement(); - writer.WriteStartElement("gradleExport"); - writer.WriteValue(kv.Value.gradleExport); - writer.WriteEndElement(); - writer.WriteStartElement("gradleTemplate"); - writer.WriteValue(kv.Value.gradleTemplate); - writer.WriteEndElement(); - writer.WriteStartElement("ignoredVersion"); - writer.WriteValue(kv.Value.ignoredVersion); - writer.WriteEndElement(); - writer.WriteEndElement(); - } - writer.WriteEndElement(); - writer.Flush(); - writer.Close(); - aarExplodeDataSaved = AarExplodeData.CopyDictionary(aarExplodeData); - } - } catch (Exception e) { - PlayServicesResolver.Log(String.Format( - "Failed to write AAR cache {0} ({1})\n" + - "Auto-resolution will be slower after recompilation.\n", aarExplodeDataFile, - e.ToString()), level: LogLevel.Warning); - } - } - - #region IResolver implementation - - /// - /// Version of the resolver. - 1.1.0 - /// - /// The resolver with the greatest version is used when resolving. - /// The value of the version is calculated using MakeVersion in DefaultResolver - /// - public override int Version() - { - return MakeVersionNumber(MajorVersion, MinorVersion, PointVersion); - } - - /// - /// Parse output of download_artifacts.gradle into lists of copied and missing artifacts. - /// - /// Standard output of the download_artifacts.gradle. - /// Directory to artifacts were copied into. - /// Returns a list of copied artifact files. - /// Returns a list of missing artifact - /// specifications. - /// Returns a list of artifact specifications that were - /// modified. - private void ParseDownloadGradleArtifactsGradleOutput( - string output, string destinationDirectory, - out List copiedArtifacts, out List missingArtifacts, - out List modifiedArtifacts) { - // Parse stdout for copied and missing artifacts. - copiedArtifacts = new List(); - missingArtifacts = new List(); - modifiedArtifacts = new List(); - string currentHeader = null; - const string COPIED_ARTIFACTS_HEADER = "Copied artifacts:"; - const string MISSING_ARTIFACTS_HEADER = "Missing artifacts:"; - const string MODIFIED_ARTIFACTS_HEADER = "Modified artifacts:"; - foreach (var line in output.Split(new string[] { "\r\n", "\n" }, - StringSplitOptions.None)) { - if (line.StartsWith(COPIED_ARTIFACTS_HEADER) || - line.StartsWith(MISSING_ARTIFACTS_HEADER) || - line.StartsWith(MODIFIED_ARTIFACTS_HEADER)) { - currentHeader = line; - continue; - } else if (String.IsNullOrEmpty(line.Trim())) { - currentHeader = null; - continue; - } - if (!String.IsNullOrEmpty(currentHeader)) { - if (currentHeader == COPIED_ARTIFACTS_HEADER) { - // Store the POSIX path of the copied package to handle Windows - // path variants. - copiedArtifacts.Add(Path.Combine(destinationDirectory, - line.Trim()).Replace("\\", "/")); - } else if (currentHeader == MISSING_ARTIFACTS_HEADER) { - missingArtifacts.Add(line.Trim()); - } else if (currentHeader == MODIFIED_ARTIFACTS_HEADER) { - modifiedArtifacts.Add(line.Trim()); - } - } - } - } - - /// - /// Log an error with the set of dependencies that were not fetched. - /// - /// List of missing dependencies. - private void LogMissingDependenciesError(List missingArtifacts) { - // Log error for missing packages. - if (missingArtifacts.Count > 0) { - PlayServicesResolver.Log( - String.Format("Resolution failed\n\n" + - "Failed to fetch the following dependencies:\n{0}\n\n", - String.Join("\n", missingArtifacts.ToArray())), - level: LogLevel.Error); - } - } - - /// - /// Escape all special characters in a gradle property value. - /// - /// Value to escape. - /// Function which generates an escaped character. By default - /// this adds "\\" to each escaped character. - /// Characters to exclude from the escaping set. - /// Escaped value. - private static string EscapeGradlePropertyValue( - string value, Func escapeFunc = null, - HashSet charactersToExclude = null) { - if (escapeFunc == null) { - escapeFunc = (characterToEscape) => { return "\\" + characterToEscape; }; - } - foreach (var characterToEscape in GradlePropertySpecialCharacters) { - if (charactersToExclude == null || - !(charactersToExclude.Contains(characterToEscape))) { - value = value.Replace(characterToEscape, escapeFunc(characterToEscape)); - } - } - return value; - } - - /// - /// Generates a Gradle (Java) properties string from a dictionary of key value pairs. - /// Details of the format is documented in - /// http://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#\ - /// store%28java.io.Writer,%20java.lang.String%29 - /// - /// Properties to generate a string from. Each value must not - /// contain a newline. - /// String with Gradle (Java) properties - private string GenerateGradleProperties(Dictionary properties) { - var lines = new List(); - foreach (var kv in properties) { - var escapedKey = kv.Key.Replace(" ", "\\ "); - var elementAfterLeadingWhitespace = kv.Value.TrimStart(new [] { ' ' }); - var escapedElement = - kv.Value.Substring(elementAfterLeadingWhitespace.Length).Replace(" ", "\\ ") + - EscapeGradlePropertyValue(elementAfterLeadingWhitespace); - lines.Add(String.Format("{0}={1}", escapedKey, escapedElement)); - } - return String.Join("\n", lines.ToArray()); - } - - /// - /// From a list of dependencies generate a list of Maven / Gradle / Ivy package spec - /// strings. - /// - /// Dependency instances to query for package specs. - /// Dictionary of Dependency instances indexed by package spec strings. - internal static Dictionary DependenciesToPackageSpecs( - IEnumerable dependencies) { - var sourcesByPackageSpec = new Dictionary(); - foreach (var dependency in dependencies) { - // Convert the legacy "LATEST" version spec to a Gradle version spec. - var packageSpec = dependency.Version.ToUpper() == "LATEST" ? - dependency.VersionlessKey + ":+" : dependency.Key; - var source = CommandLine.SplitLines(dependency.CreatedBy)[0]; - string sources; - if (sourcesByPackageSpec.TryGetValue(packageSpec, out sources)) { - sources = sources + ", " + source; - } else { - sources = source; - } - sourcesByPackageSpec[packageSpec] = sources; - } - return sourcesByPackageSpec; - } - - /// - /// Convert a repo path to a valid URI - /// - /// Repo path to convert. - /// URI to the repo. - private static string RepoPathToUri(string repoPath) { - // Filter Android SDK repos as they're supplied in the build script. - if (repoPath.StartsWith(PlayServicesSupport.SdkVariable)) return null; - // Since we need a URL, determine whether the repo has a scheme. If not, - // assume it's a local file. - bool validScheme = false; - foreach (var scheme in new [] { "file:", "http:", "https:" }) { - validScheme |= repoPath.StartsWith(scheme); - } - if (!validScheme) { - repoPath = "file:///" + Path.GetFullPath(repoPath).Replace("\\", "/"); - } - // Escape the URI to handle special characters like spaces and percent escape - // all characters that are interpreted by gradle. - return EscapeGradlePropertyValue(Uri.EscapeUriString(repoPath), - escapeFunc: Uri.EscapeDataString, - charactersToExclude: GradleUriExcludeEscapeCharacters); - } - - /// - /// Extract the ordered set of repository URIs from the specified dependencies. - /// - /// Dependency instances to query for repos. - /// Dictionary of source filenames by repo names. - internal static List> DependenciesToRepoUris( - IEnumerable dependencies) { - var sourcesByRepo = new OrderedDictionary(); - Action addToSourcesByRepo = (repo, source) => { - if (!String.IsNullOrEmpty(repo)) { - if (sourcesByRepo.Contains(repo)) { - var sources = (List)sourcesByRepo[repo]; - if (!sources.Contains(source)) { - sources.Add(source); - } - } else { - sourcesByRepo[repo] = new List() { source }; - } - } - }; - // Add global repos first. - foreach (var kv in PlayServicesSupport.AdditionalRepositoryPaths) { - addToSourcesByRepo(RepoPathToUri(kv.Key), kv.Value); - } - // Build array of repos to search, they're interleaved across all dependencies as the - // order matters. - int maxNumberOfRepos = 0; - foreach (var dependency in dependencies) { - maxNumberOfRepos = Math.Max(maxNumberOfRepos, dependency.Repositories.Length); - } - for (int i = 0; i < maxNumberOfRepos; i++) { - foreach (var dependency in dependencies) { - var repos = dependency.Repositories; - if (i >= repos.Length) continue; - addToSourcesByRepo(RepoPathToUri(repos[i]), - CommandLine.SplitLines(dependency.CreatedBy)[0]); - } - } - var sourcesByRepoList = new List>(); - var enumerator = sourcesByRepo.GetEnumerator(); - while (enumerator.MoveNext()) { - sourcesByRepoList.Add( - new KeyValuePair( - (string)enumerator.Key, - String.Join(", ", ((List)enumerator.Value).ToArray()))); - } - return sourcesByRepoList; - } - - // Holds Gradle resolution state. - private class ResolutionState { - public CommandLine.Result commandLineResult = new CommandLine.Result(); - public List copiedArtifacts = new List(); - public HashSet copiedArtifactsSet = new HashSet(); - public List missingArtifacts = new List(); - public List missingArtifactsAsDependencies = new List(); - public List modifiedArtifacts = new List(); - public bool errorOrWarningLogged = false; - } - - /// - /// Perform resolution using Gradle. - /// - /// Directory to copy packages into. - /// Path to the Android SDK. This is required as - /// PlayServicesSupport.SDK can only be called from the main thread. - /// Log errors when artifacts are missing. - /// Called when resolution is complete with a list of - /// packages that were not found. - private void GradleResolution( - string destinationDirectory, string androidSdkPath, - bool logErrorOnMissingArtifacts, - System.Action> resolutionComplete) { - var gradleWrapper = Path.GetFullPath(Path.Combine( - gradleBuildDirectory, - UnityEngine.RuntimePlatform.WindowsEditor == UnityEngine.Application.platform ? - "gradlew.bat" : "gradlew")); - var buildScript = Path.GetFullPath(Path.Combine( - gradleBuildDirectory, EMBEDDED_RESOURCES_NAMESPACE + "download_artifacts.gradle")); - // Get all dependencies. - var allDependencies = PlayServicesSupport.GetAllDependencies(); - var allDependenciesList = new List(allDependencies.Values); - - // Extract Gradle wrapper and the build script to the build directory. - if (!(Directory.Exists(gradleBuildDirectory) && File.Exists(gradleWrapper) && - File.Exists(buildScript))) { - var gradleTemplateZip = Path.Combine( - gradleBuildDirectory, EMBEDDED_RESOURCES_NAMESPACE + "gradle-template.zip"); - foreach (var resource in new [] { gradleTemplateZip, buildScript }) { - ExtractResource(Path.GetFileName(resource), resource); - } - if (!PlayServicesResolver.ExtractZip(gradleTemplateZip, new [] { - "gradle/wrapper/gradle-wrapper.jar", - "gradle/wrapper/gradle-wrapper.properties", - "gradlew", - "gradlew.bat"}, gradleBuildDirectory)) { - PlayServicesResolver.Log( - String.Format("Unable to extract Gradle build component {0}\n\n" + - "Resolution failed.", gradleTemplateZip), - level: LogLevel.Error); - resolutionComplete(allDependenciesList); - return; - } - // Files extracted from the zip file don't have the executable bit set on some - // platforms, so set it here. - // Unfortunately, File.GetAccessControl() isn't implemented, so we'll use - // chmod (OSX / Linux) and on Windows extracted files are executable by default - // so we do nothing. - if (UnityEngine.RuntimePlatform.WindowsEditor != - UnityEngine.Application.platform) { - var result = CommandLine.Run("chmod", - String.Format("ug+x \"{0}\"", gradleWrapper)); - if (result.exitCode != 0) { - PlayServicesResolver.Log( - String.Format("Failed to make \"{0}\" executable.\n\n" + - "Resolution failed.\n\n{1}", gradleWrapper, - result.message), - level: LogLevel.Error); - resolutionComplete(allDependenciesList); - return; - } - } - } - - // Build array of repos to search, they're interleaved across all dependencies as the - // order matters. - var repoList = new List(); - foreach (var kv in DependenciesToRepoUris(allDependencies.Values)) repoList.Add(kv.Key); - - // Create an instance of ResolutionState to aggregate the results. - var resolutionState = new ResolutionState(); - - // Window used to display resolution progress. - var window = CommandLineDialog.CreateCommandLineDialog( - "Resolving Android Dependencies"); - - // Register an event to redirect log messages to the resolution window. - Google.Logger.LogMessageDelegate logToWindow = (message, logLevel) => { - string messagePrefix; - switch (logLevel) { - case LogLevel.Error: - messagePrefix = "ERROR: "; - resolutionState.errorOrWarningLogged = true; - break; - case LogLevel.Warning: - messagePrefix = "WARNING: "; - resolutionState.errorOrWarningLogged = true; - break; - default: - messagePrefix = ""; - break; - } - if (!window.RunningCommand) { - window.AddBodyText(messagePrefix + message + "\n"); - } - }; - PlayServicesResolver.logger.LogMessage += logToWindow; - - // When resolution is complete unregister the log redirection event. - Action resolutionCompleteRestoreLogger = () => { - PlayServicesResolver.logger.LogMessage -= logToWindow; - // If the command completed successfully or the log level is info or above close - // the window, otherwise leave it open for inspection. - if (resolutionState.commandLineResult.exitCode == 0 && - PlayServicesResolver.logger.Level >= LogLevel.Info && - !resolutionState.errorOrWarningLogged) { - window.Close(); - } - resolutionComplete(resolutionState.missingArtifactsAsDependencies); - }; - - // Executed after refreshing the explode cache. - Action processAars = () => { - // Find all labeled files that were not copied and delete them. - var staleArtifacts = new HashSet(); - foreach (var assetPath in PlayServicesResolver.FindLabeledAssets()) { - if (!resolutionState.copiedArtifactsSet.Contains( - assetPath.Replace("\\", "/"))) { - staleArtifacts.Add(assetPath); - } - } - if (staleArtifacts.Count > 0) { - PlayServicesResolver.Log( - String.Format("Deleting stale dependencies:\n{0}", - String.Join("\n", - (new List(staleArtifacts)).ToArray())), - level: LogLevel.Verbose); - var deleteFailures = new List(); - foreach (var assetPath in staleArtifacts) { - deleteFailures.AddRange(FileUtils.DeleteExistingFileOrDirectory(assetPath)); - } - var deleteError = FileUtils.FormatError("Failed to delete stale artifacts", - deleteFailures); - if (!String.IsNullOrEmpty(deleteError)) { - PlayServicesResolver.Log(deleteError, level: LogLevel.Error); - } - } - // Process / explode copied AARs. - ProcessAars( - destinationDirectory, new HashSet(resolutionState.copiedArtifacts), - (progress, message) => { - window.SetProgress("Processing libraries...", progress, message); - }, - () => { - // Look up the original Dependency structure for each missing artifact. - resolutionState.missingArtifactsAsDependencies = new List(); - foreach (var artifact in resolutionState.missingArtifacts) { - Dependency dep; - if (!allDependencies.TryGetValue(artifact, out dep)) { - // If this fails, something may have gone wrong with the Gradle - // script. Rather than failing hard, fallback to recreating the - // Dependency class with the partial data we have now. - var components = new List( - artifact.Split(new char[] { ':' })); - if (components.Count < 2) { - PlayServicesResolver.Log( - String.Format( - "Found invalid missing artifact {0}\n" + - "Something went wrong with the gradle artifact " + - "download script\n." + - "Please report a bug", artifact), - level: LogLevel.Warning); - continue; - } else if (components.Count < 3 || components[2] == "+") { - components.Add("LATEST"); - } - dep = new Dependency(components[0], components[1], components[2]); - } - resolutionState.missingArtifactsAsDependencies.Add(dep); - } - if (logErrorOnMissingArtifacts) { - LogMissingDependenciesError(resolutionState.missingArtifacts); - } - resolutionCompleteRestoreLogger(); - }); - }; - - // Executed after labeling copied assets. - Action refreshExplodeCache = () => { - // Poke the explode cache for each copied file and add the exploded paths to the - // output list set. - resolutionState.copiedArtifactsSet = - new HashSet(resolutionState.copiedArtifacts); - var numberOfCopiedArtifacts = resolutionState.copiedArtifacts.Count; - var artifactsToInspect = new List(resolutionState.copiedArtifacts); - if (numberOfCopiedArtifacts > 0) { - RunOnMainThread.PollOnUpdateUntilComplete(() => { - var remaining = artifactsToInspect.Count; - if (remaining == 0) { - processAars(); - return true; - } - var artifact = artifactsToInspect[0]; - artifactsToInspect.RemoveAt(0); - window.SetProgress("Inspecting libraries...", - (float)(numberOfCopiedArtifacts - remaining) / - (float)numberOfCopiedArtifacts, artifact); - if (ShouldExplode(artifact)) { - resolutionState.copiedArtifactsSet.Add( - DetermineExplodedAarPath(artifact)); - } - return false; - }); - } - }; - - // Executed when Gradle finishes execution. - CommandLine.CompletionHandler gradleComplete = (commandLineResult) => { - resolutionState.commandLineResult = commandLineResult; - if (commandLineResult.exitCode != 0) { - resolutionState.missingArtifactsAsDependencies = allDependenciesList; - PlayServicesResolver.Log( - String.Format("Gradle failed to fetch dependencies.\n\n{0}", - commandLineResult.message), level: LogLevel.Error); - resolutionCompleteRestoreLogger(); - return; - } - // Parse stdout for copied and missing artifacts. - ParseDownloadGradleArtifactsGradleOutput(commandLineResult.stdout, - destinationDirectory, - out resolutionState.copiedArtifacts, - out resolutionState.missingArtifacts, - out resolutionState.modifiedArtifacts); - // Display a warning about modified artifact versions. - if (resolutionState.modifiedArtifacts.Count > 0) { - PlayServicesResolver.Log( - String.Format( - "Some conflicting dependencies were found.\n" + - "The following dependency versions were modified:\n" + - "{0}\n", - String.Join("\n", resolutionState.modifiedArtifacts.ToArray())), - level: LogLevel.Warning); - } - // Label all copied files. - PlayServicesResolver.LabelAssets( - resolutionState.copiedArtifacts, - complete: (unusedUnlabeled) => { refreshExplodeCache(); }, - synchronous: false, - progressUpdate: (progress, message) => { - window.SetProgress("Labeling libraries...", progress, message); - }); - }; - - var packageSpecs = - new List(DependenciesToPackageSpecs(allDependencies.Values).Keys); - - var gradleProjectProperties = new Dictionary() { - { "ANDROID_HOME", androidSdkPath }, - { "TARGET_DIR", Path.GetFullPath(destinationDirectory) }, - { "MAVEN_REPOS", String.Join(";", repoList.ToArray()) }, - { "PACKAGES_TO_COPY", String.Join(";", packageSpecs.ToArray()) } - }; - var gradleArguments = new List { - String.Format("-b \"{0}\"", buildScript), - SettingsDialog.UseGradleDaemon ? "--daemon" : "--no-daemon", - }; - foreach (var kv in gradleProjectProperties) { - gradleArguments.Add(String.Format("\"-P{0}={1}\"", kv.Key, kv.Value)); - } - var gradleArgumentsString = String.Join(" ", gradleArguments.ToArray()); - - // Generate gradle.properties to set properties in the script rather than using - // the command line. - // Some users of Windows 10 systems have had issues running the Gradle resolver - // which is suspected to be caused by command line argument parsing. - // Using both gradle.properties and properties specified via command line arguments - // works fine. - File.WriteAllText(Path.Combine(gradleBuildDirectory, "gradle.properties"), - GenerateGradleProperties(gradleProjectProperties)); - - PlayServicesResolver.Log(String.Format("Running dependency fetching script\n" + - "\n" + - "{0} {1}\n", - gradleWrapper, gradleArgumentsString), - level: LogLevel.Verbose); - - // Run the build script to perform the resolution popping up a window in the editor. - window.summaryText = "Resolving Android Dependencies..."; - window.modal = false; - window.progressTitle = window.summaryText; - window.autoScrollToBottom = true; - window.logger = PlayServicesResolver.logger; - var maxProgressLines = (allDependenciesList.Count * 10) + 50; - window.RunAsync(gradleWrapper, gradleArgumentsString, - (result) => { RunOnMainThread.Run(() => { gradleComplete(result); }); }, - workingDirectory: gradleBuildDirectory, - maxProgressLines: maxProgressLines); - window.Show(); - } - - /// - /// Search the project for AARs & JARs that could conflict with each other and resolve - /// the conflicts if possible. - /// - /// - /// This handles the following cases: - /// 1. If any libraries present match the name play-services-* and google-play-services.jar - /// is included in the project the user will be warned of incompatibility between - /// the legacy JAR and the newer AAR libraries. - /// 2. If a managed (labeled) library conflicting with one or more versions of unmanaged - /// (e.g play-services-base-10.2.3.aar (managed) vs. play-services-10.2.2.aar (unmanaged) - /// and play-services-base-9.2.4.aar (unmanaged)) - /// The user is warned about the unmanaged conflicting libraries and, if they're - /// older than the managed library, prompted to delete the unmanaged libraries. - private void FindAndResolveConflicts() { - Func getVersionlessArtifactFilename = (filename) => { - var basename = Path.GetFileName(filename); - int split = basename.LastIndexOf("-"); - return split >= 0 ? basename.Substring(0, split) : basename; - }; - var managedPlayServicesArtifacts = new List(); - // Gather artifacts managed by the resolver indexed by versionless name. - var managedArtifacts = new Dictionary(); - var managedArtifactFilenames = new HashSet(); - foreach (var filename in PlayServicesResolver.FindLabeledAssets()) { - var artifact = getVersionlessArtifactFilename(filename); - // Ignore non-existent files as it's possible for the asset database to reference - // missing files if it hasn't been refreshed or completed a refresh. - if (File.Exists(filename) || Directory.Exists(filename)) { - managedArtifacts[artifact] = filename; - if (artifact.StartsWith("play-services-") || - artifact.StartsWith("com.google.android.gms.play-services-")) { - managedPlayServicesArtifacts.Add(filename); - } - } - } - managedArtifactFilenames.UnionWith(managedArtifacts.Values); - - // Gather all artifacts (AARs, JARs) that are not managed by the resolver. - var unmanagedArtifacts = new Dictionary>(); - var packagingExtensions = new HashSet(Dependency.Packaging); - // srcaar files are ignored by Unity so are not included in the build. - packagingExtensions.Remove(".srcaar"); - // List of paths to the legacy google-play-services.jar - var playServicesJars = new List(); - const string playServicesJar = "google-play-services.jar"; - foreach (var assetGuid in AssetDatabase.FindAssets("t:Object")) { - var filename = AssetDatabase.GUIDToAssetPath(assetGuid); - // Ignore all assets that are managed by the plugin and, since the asset database - // could be stale at this point, check the file exists. - if (!managedArtifactFilenames.Contains(filename) && - (File.Exists(filename) || Directory.Exists(filename))) { - if (Path.GetFileName(filename).ToLower() == playServicesJar) { - playServicesJars.Add(filename); - } else if (packagingExtensions.Contains( - Path.GetExtension(filename).ToLower())) { - var versionlessFilename = getVersionlessArtifactFilename(filename); - List existing; - var unmanaged = unmanagedArtifacts.TryGetValue( - versionlessFilename, out existing) ? existing : new List(); - unmanaged.Add(filename); - unmanagedArtifacts[versionlessFilename] = unmanaged; - } - } - } - - // Check for conflicting Play Services versions. - // It's not possible to resolve this automatically as google-play-services.jar used to - // include all libraries so we don't know the set of components the developer requires. - if (managedPlayServicesArtifacts.Count > 0 && playServicesJars.Count > 0) { - PlayServicesResolver.Log( - String.Format( - "Legacy {0} found!\n\n" + - "Your application will not build in the current state.\n" + - "{0} library (found in the following locations):\n" + - "{1}\n" + - "\n" + - "{0} is incompatible with plugins that use newer versions of Google\n" + - "Play services (conflicting libraries in the following locations):\n" + - "{2}\n" + - "\n" + - "To resolve this issue find the plugin(s) that use\n" + - "{0} and either add newer versions of the required libraries or\n" + - "contact the plugin vendor to do so.\n\n", - playServicesJar, String.Join("\n", playServicesJars.ToArray()), - String.Join("\n", managedPlayServicesArtifacts.ToArray())), - level: LogLevel.Warning); - } - - // For each managed artifact aggregate the set of conflicting unmanaged artifacts. - var conflicts = new Dictionary>(); - foreach (var managed in managedArtifacts) { - List unmanagedFilenames; - if (unmanagedArtifacts.TryGetValue(managed.Key, out unmanagedFilenames)) { - // Found a conflict - List existingConflicts; - var unmanagedConflicts = conflicts.TryGetValue( - managed.Value, out existingConflicts) ? - existingConflicts : new List(); - unmanagedConflicts.AddRange(unmanagedFilenames); - conflicts[managed.Value] = unmanagedConflicts; - } - } - - // Warn about each conflicting version and attempt to resolve each conflict by removing - // older unmanaged versions. - Func getVersionFromFilename = (filename) => { - string basename = Path.GetFileNameWithoutExtension(Path.GetFileName(filename)); - return basename.Substring(getVersionlessArtifactFilename(basename).Length + 1); - }; - foreach (var conflict in conflicts) { - var currentVersion = getVersionFromFilename(conflict.Key); - var conflictingVersionsSet = new HashSet(); - foreach (var conflictFilename in conflict.Value) { - conflictingVersionsSet.Add(getVersionFromFilename(conflictFilename)); - } - var conflictingVersions = new List(conflictingVersionsSet); - conflictingVersions.Sort(Dependency.versionComparer); - - var warningMessage = String.Format( - "Found conflicting Android library {0}\n" + - "\n" + - "{1} (managed by the Android Resolver) conflicts with:\n" + - "{2}\n", - getVersionlessArtifactFilename(conflict.Key), - conflict.Key, String.Join("\n", conflict.Value.ToArray())); - - // If the conflicting versions are older than the current version we can - // possibly clean up the old versions automatically. - if (Dependency.versionComparer.Compare(conflictingVersions[0], - currentVersion) >= 0) { - if (EditorUtility.DisplayDialog( - "Resolve Conflict?", - warningMessage + - "\n" + - "The conflicting libraries are older than the library managed by " + - "the Android Resolver. Would you like to remove the old libraries " + - "to resolve the conflict?", - "Yes", "No")) { - var deleteFailures = new List(); - foreach (var filename in conflict.Value) { - deleteFailures.AddRange( - FileUtils.DeleteExistingFileOrDirectory(filename)); - } - var deleteError = FileUtils.FormatError("Unable to delete old libraries", - deleteFailures); - if (!String.IsNullOrEmpty(deleteError)) { - PlayServicesResolver.Log(deleteError, level: LogLevel.Error); - } - warningMessage = null; - } - } - - if (!String.IsNullOrEmpty(warningMessage)) { - PlayServicesResolver.Log( - warningMessage + - "\n" + - "Your application is unlikely to build in the current state.\n" + - "\n" + - "To resolve this problem you can try one of the following:\n" + - "* Updating the dependencies managed by the Android Resolver\n" + - " to remove references to old libraries. Be careful to not\n" + - " include conflicting versions of Google Play services.\n" + - "* Contacting the plugin vendor(s) with conflicting\n" + - " dependencies and asking them to update their plugin.\n", - level: LogLevel.Warning); - } - } - } - - /// - /// Perform the resolution and the exploding/cleanup as needed. - /// - public override void DoResolution( - PlayServicesSupport svcSupport, string destinationDirectory, - System.Action resolutionComplete) { - // Run resolution on the main thread to serialize the operation as DoResolutionUnsafe - // is not thread safe. - System.Action resolve = () => { - System.Action unlockResolveAndSignalResolutionComplete = () => { - FindAndResolveConflicts(); - lock (resolveLock) { - resolveActionActive = null; - } - resolutionComplete(); - }; - DoResolutionUnsafe(svcSupport, destinationDirectory, - unlockResolveAndSignalResolutionComplete); - }; - lock (resolveLock) { - resolveUpdateQueue.Enqueue(resolve); - RunOnMainThread.Run(UpdateTryResolution); - } - } - - // Try executing a resolution. - private static void UpdateTryResolution() { - lock (resolveLock) { - if (resolveActionActive == null) { - if (resolveUpdateQueue.Count > 0) { - resolveActionActive = (System.Action)resolveUpdateQueue.Dequeue(); - resolveActionActive(); - } - } - } - } - - /// - /// Perform the resolution and the exploding/cleanup as needed. - /// This is *not* thread safe. - /// - private void DoResolutionUnsafe( - PlayServicesSupport svcSupport, string destinationDirectory, - System.Action resolutionComplete) - { - // Cache the setting as it can only be queried from the main thread. - var sdkPath = PlayServicesResolver.AndroidSdkRoot; - // If the Android SDK path isn't set or doesn't exist report an error. - if (String.IsNullOrEmpty(sdkPath) || !Directory.Exists(sdkPath)) { - PlayServicesResolver.Log(String.Format( - "Android dependency resolution failed, your application will probably " + - "not run.\n\n" + - "Android SDK path must be set to a valid directory ({0})\n" + - "This must be configured in the 'Preference > External Tools > Android SDK'\n" + - "menu option.\n", String.IsNullOrEmpty(sdkPath) ? "{none}" : sdkPath), - level: LogLevel.Error); - resolutionComplete(); - return; - } - - System.Action resolve = () => { - PlayServicesResolver.Log("Performing Android Dependency Resolution", - LogLevel.Verbose); - GradleResolution(destinationDirectory, sdkPath, true, - (missingArtifacts) => { resolutionComplete(); }); - }; - - System.Action> reportOrInstallMissingArtifacts = - (List requiredDependencies) => { - // Set of packages that need to be installed. - var installPackages = new HashSet(); - // Retrieve the set of required packages and whether they're installed. - var requiredPackages = new Dictionary>(); - - if (requiredDependencies.Count == 0) { - resolutionComplete(); - return; - } - foreach (Dependency dependency in requiredDependencies) { - PlayServicesResolver.Log( - String.Format("Missing Android component {0} (Android SDK Packages: {1})", - dependency.Key, dependency.PackageIds != null ? - String.Join(",", dependency.PackageIds) : "(none)"), - level: LogLevel.Verbose); - if (dependency.PackageIds != null) { - foreach (string packageId in dependency.PackageIds) { - HashSet dependencySet; - if (!requiredPackages.TryGetValue(packageId, out dependencySet)) { - dependencySet = new HashSet(); - } - dependencySet.Add(dependency.Key); - requiredPackages[packageId] = dependencySet; - // Install / update the Android SDK package that hosts this dependency. - installPackages.Add(new AndroidSdkPackageNameVersion { - LegacyName = packageId - }); - } - } - } - - // If no packages need to be installed or Android SDK package installation is - // disabled. - if (installPackages.Count == 0 || !AndroidPackageInstallationEnabled()) { - // Report missing packages as warnings and try to resolve anyway. - foreach (var pkg in requiredPackages.Keys) { - var packageNameVersion = new AndroidSdkPackageNameVersion { - LegacyName = pkg }; - var depString = System.String.Join( - "\n", (new List(requiredPackages[pkg])).ToArray()); - if (installPackages.Contains(packageNameVersion)) { - PlayServicesResolver.Log( - String.Format( - "Android SDK package {0} is not installed or out of " + - "date.\n\n" + - "This is required by the following dependencies:\n" + - "{1}", pkg, depString), - level: LogLevel.Warning); - } - } - // At this point we've already tried resolving with Gradle. Therefore, - // Android SDK package installation is disabled or not required trying - // to resolve again only repeats the same operation we've already - // performed. So we just report report the missing artifacts as an error - // and abort. - var missingArtifacts = new List(); - foreach (var dep in requiredDependencies) missingArtifacts.Add(dep.Key); - LogMissingDependenciesError(missingArtifacts); - resolutionComplete(); - return; - } - InstallAndroidSdkPackagesAndResolve(sdkPath, installPackages, - requiredPackages, resolve); - }; - - GradleResolution(destinationDirectory, sdkPath, - !AndroidPackageInstallationEnabled(), - reportOrInstallMissingArtifacts); - } - - /// - /// Run the SDK manager to install the specified set of packages then attempt resolution - /// again. - /// - /// Path to the Android SDK. - /// Set of Android SDK packages to install. - /// The set dependencies for each Android SDK package. - /// This is used to report which dependencies can't be installed if Android SDK package - /// installation fails. - /// Action that performs resolution. - private void InstallAndroidSdkPackagesAndResolve( - string sdkPath, HashSet installPackages, - Dictionary> requiredPackages, System.Action resolve) { - // Find / upgrade the Android SDK manager. - AndroidSdkManager.Create( - sdkPath, - (IAndroidSdkManager sdkManager) => { - if (sdkManager == null) { - PlayServicesResolver.Log( - String.Format( - "Unable to find the Android SDK manager tool.\n\n" + - "The following Required Android packages cannot be installed:\n" + - "{0}\n" + - "\n" + - "{1}\n", - AndroidSdkPackageNameVersion.ListToString(installPackages), - String.IsNullOrEmpty(sdkPath) ? - PlayServicesSupport.AndroidSdkConfigurationError : ""), - level: LogLevel.Error); - return; - } - // Get the set of available and installed packages. - sdkManager.QueryPackages( - (AndroidSdkPackageCollection packages) => { - if (packages == null) return; - - // Filter the set of packages to install by what is available. - foreach (var packageName in requiredPackages.Keys) { - var pkg = new AndroidSdkPackageNameVersion { - LegacyName = packageName - }; - var depString = System.String.Join( - "\n", - (new List(requiredPackages[packageName])).ToArray()); - var availablePackage = - packages.GetMostRecentAvailablePackage(pkg.Name); - if (availablePackage == null || !availablePackage.Installed) { - PlayServicesResolver.Log( - String.Format( - "Android SDK package {0} ({1}) {2}\n\n" + - "This is required by the following dependencies:\n" + - "{3}\n", pkg.Name, pkg.LegacyName, - availablePackage != null ? - "not installed or out of date." : - "not available for installation.", - depString), - level: LogLevel.Warning); - if (availablePackage == null) { - installPackages.Remove(pkg); - } else if (!availablePackage.Installed) { - installPackages.Add(availablePackage); - } - } - } - if (installPackages.Count == 0) { - resolve(); - return; - } - // Start installation. - sdkManager.InstallPackages( - installPackages, (bool success) => { resolve(); }); - }); - }); - } - - - /// - /// Called during Update to allow the resolver to check any build settings of managed - /// packages to see whether resolution should be triggered again. - /// - /// Array of packages that should be re-resolved if resolution should occur, - /// null otherwise. - public override string[] OnBuildSettings() { - // Determine which packages need to be updated. - List packagesToUpdate = new List(); - List aarsToResolve = new List(); - var aarExplodeDataCopy = new Dictionary(aarExplodeData); - foreach (var kv in aarExplodeDataCopy) { - var aar = kv.Key; - var aarData = kv.Value; - // If the cached file has been removed, ditch it from the cache. - if (!(File.Exists(aarData.path) || Directory.Exists(aarData.path))) { - PlayServicesResolver.Log(String.Format("Found missing AAR {0}", aarData.path), - level: LogLevel.Verbose); - aarsToResolve.Add(aar); - } else if (AarExplodeDataIsDirty(aarData)) { - PlayServicesResolver.Log(String.Format("{0} needs to be refreshed ({1})", - aarData.path, aarData.ToString()), - level: LogLevel.Verbose); - packagesToUpdate.Add(aarData.path); - aarsToResolve.Add(aar); - } - } - // Remove AARs that will be resolved from the dictionary so the next call to - // OnBundleId triggers another resolution process. - foreach (string aar in aarsToResolve) aarExplodeData.Remove(aar); - SaveAarExplodeCache(); - if (packagesToUpdate.Count == 0) return null; - string[] packagesToUpdateArray = packagesToUpdate.ToArray(); - PlayServicesResolver.Log( - String.Format("OnBuildSettings, Packages to update ({0})", - String.Join(", ", packagesToUpdateArray)), - level: LogLevel.Verbose); - return packagesToUpdateArray; - } - - #endregion - - /// - /// Convert an AAR filename to package name. - /// - /// Path of the AAR to convert. - /// AAR package name. - private string AarPathToPackageName(string aarPath) { - var aarFilename = Path.GetFileName(aarPath); - foreach (var extension in Dependency.Packaging) { - if (aarPath.EndsWith(extension)) { - return aarFilename.Substring(0, aarFilename.Length - extension.Length); - } - } - return aarFilename; - } - - /// - /// Search the cache for an entry associated with the specified AAR path. - /// - /// Path of the AAR to query. - /// AarExplodeData if the entry is found in the cache, null otherwise. - private AarExplodeData FindAarExplodeDataEntry(string aarPath) { - var aarFilename = AarPathToPackageName(aarPath); - AarExplodeData aarData = null; - // The argument to this method could be the exploded folder rather than the source - // package (e.g some-package rather than some-package.aar). Therefore search the - // cache for the original / unexploded package if the specified file isn't found. - var packageExtensions = new List(); - packageExtensions.Add(""); // Search for aarFilename first. - packageExtensions.AddRange(Dependency.Packaging); - foreach (var extension in packageExtensions) { - AarExplodeData data; - if (aarExplodeData.TryGetValue(aarFilename + extension, out data)) { - aarData = data; - break; - } - } - return aarData; - } - - /// - /// Whether an Ant project should be generated for an artifact. - /// - /// Whether the artifact needs to be exploded so that it can be - /// modified. - private static bool GenerateAntProject(bool explode) { - return explode && !PlayServicesResolver.GradleBuildEnabled; - } - - /// - /// Determine whether a package is dirty in the AAR cache. - /// - /// Path of the AAR to query. - /// true if the cache entry is dirty, false otherwise. - private bool AarExplodeDataIsDirty(AarExplodeData aarData) { - if (aarData.bundleId != PlayServicesResolver.GetAndroidApplicationId()) { - PlayServicesResolver.Log( - String.Format("{0}: Bundle ID changed {1} --> {2}", aarData.path, - aarData.bundleId, - PlayServicesResolver.GetAndroidApplicationId()), - level: LogLevel.Verbose); - return true; - } - var availableAbis = aarData.AvailableAbis; - var targetAbis = aarData.TargetAbis; - var currentAbis = AndroidAbis.Current; - if (targetAbis != null && availableAbis != null && - !targetAbis.Equals(AndroidAbis.Current)) { - PlayServicesResolver.Log( - String.Format("{0}: Target ABIs changed {1} --> {2}", aarData.path, - targetAbis.ToString(), currentAbis.ToString()), - level: LogLevel.Verbose); - return true; - } - if (aarData.gradleBuildSystem != PlayServicesResolver.GradleBuildEnabled) { - PlayServicesResolver.Log( - String.Format("{0}: Gradle build system enabled changed {1} --> {2}", - aarData.path, aarData.gradleBuildSystem, - PlayServicesResolver.GradleBuildEnabled), - level: LogLevel.Verbose); - return true; - } - if (aarData.gradleExport != PlayServicesResolver.GradleProjectExportEnabled) { - PlayServicesResolver.Log( - String.Format("{0}: Gradle export settings changed {1} --> {2}", - aarData.path, aarData.gradleExport, - PlayServicesResolver.GradleProjectExportEnabled), - level: LogLevel.Verbose); - return true; - } - if (aarData.gradleTemplate != PlayServicesResolver.GradleTemplateEnabled) { - PlayServicesResolver.Log( - String.Format("{0}: Gradle template changed {1} --> {2}", - aarData.path, aarData.gradleTemplate, - PlayServicesResolver.GradleTemplateEnabled), - level: LogLevel.Verbose); - return true; - } - bool generateAntProject = GenerateAntProject(aarData.explode); - if (generateAntProject && !Directory.Exists(aarData.path)) { - PlayServicesResolver.Log( - String.Format("{0}: Should be exploded but artifact directory missing.", - aarData.path), - level: LogLevel.Verbose); - return true; - } else if (!generateAntProject && !File.Exists(aarData.path)) { - PlayServicesResolver.Log( - String.Format("{0}: Should *not* be exploded but artifact file missing.", - aarData.path), - level: LogLevel.Verbose); - return true; - } - return false; - } - - /// - /// Get the target path for an exploded AAR. - /// - /// AAR file to explode. - /// Exploded AAR path. - private string DetermineExplodedAarPath(string aarPath) { - return Path.Combine(GooglePlayServices.SettingsDialog.PackageDir, - AarPathToPackageName(aarPath)); - } - - /// - /// Get the path of an existing AAR or exploded directory within the target directory. - /// - /// Name of the artifact to search for. - /// Path to the artifact if found, null otherwise. - private string FindAarInTargetPath(string aarPath) { - var basePath = DetermineExplodedAarPath(aarPath); - if (Directory.Exists(basePath)) return basePath; - foreach (var extension in Dependency.Packaging) { - var packagePath = basePath + extension; - if (File.Exists(packagePath)) return packagePath; - } - return null; - } - - /// - /// Processes the aars. - /// - /// Each aar copied is inspected and determined if it should be - /// exploded into a directory or not. Unneeded exploded directories are - /// removed. - /// - /// Exploding is needed if the version of Unity is old, or if the artifact - /// has been explicitly flagged for exploding. This allows the subsequent - /// processing of variables in the AndroidManifest.xml file which is not - /// supported by the current versions of the manifest merging process that - /// Unity uses. - /// - /// The directory to process. - /// Set of files that were recently updated and should be - /// processed. - /// Called with the progress (0..1) and message that indicates - /// processing progress. - /// Executed when this process is complete. - private void ProcessAars(string dir, HashSet updatedFiles, - Action progressUpdate, Action complete) { - // Get set of AAR files and directories we're managing. - var uniqueAars = new HashSet(PlayServicesResolver.FindLabeledAssets()); - foreach (var aarData in aarExplodeData.Values) uniqueAars.Add(aarData.path); - var aars = new Queue(uniqueAars); - - int numberOfAars = aars.Count; - if (numberOfAars == 0) { - complete(); - return; - } - // Processing can be slow so execute incrementally so we don't block the update thread. - RunOnMainThread.PollOnUpdateUntilComplete(() => { - int remainingAars = aars.Count; - bool allAarsProcessed = remainingAars == 0; - // Since the completion callback can trigger an update, remove this closure from - // the polling job list if complete. - if (allAarsProcessed) return true; - int processedAars = numberOfAars - remainingAars; - string aarPath = aars.Dequeue(); - remainingAars--; - allAarsProcessed = remainingAars == 0; - float progress = (float)processedAars / (float)numberOfAars; - try { - progressUpdate(progress, aarPath); - bool explode = ShouldExplode(aarPath); - var aarData = FindAarExplodeDataEntry(aarPath); - PlayServicesResolver.Log( - String.Format("Processing {0} ({1})", aarPath, aarData.ToString()), - level: LogLevel.Verbose); - if (AarExplodeDataIsDirty(aarData) || updatedFiles.Contains(aarPath)) { - if (explode && File.Exists(aarPath)) { - AndroidAbis abis = null; - if (!ProcessAar(Path.GetFullPath(dir), aarPath, - GenerateAntProject(explode), out abis)) { - PlayServicesResolver.Log(String.Format( - "Failed to process {0}, your Android build will fail.\n" + - "See previous error messages for failure details.\n", - aarPath)); - } - aarData.AvailableAbis = abis; - } else if (aarPath != aarData.path) { - // Clean up previously expanded / exploded versions of the AAR. - PlayServicesResolver.Log( - String.Format("Cleaning up previously exploded AAR {0}", - aarPath), - level: LogLevel.Verbose); - var explodedPath = DetermineExplodedAarPath(aarPath); - var deleteError = FileUtils.FormatError( - String.Format("Failed to delete exploded AAR directory {0}", - explodedPath), - FileUtils.DeleteExistingFileOrDirectory(explodedPath)); - if (!String.IsNullOrEmpty(deleteError)) { - PlayServicesResolver.Log(deleteError, level: LogLevel.Error); - } - } - aarData.gradleBuildSystem = PlayServicesResolver.GradleBuildEnabled; - aarData.gradleExport = PlayServicesResolver.GradleProjectExportEnabled; - aarData.gradleTemplate = PlayServicesResolver.GradleTemplateEnabled; - aarData.TargetAbis = AndroidAbis.Current; - } - SaveAarExplodeCache(); - } finally { - if (allAarsProcessed) { - progressUpdate(1.0f, "Library processing complete"); - complete(); - } - } - return allAarsProcessed; - }); - } - - /// - /// Determines whether an aar file should be exploded (extracted). - /// - /// This is required for some aars so that the Unity Jar Resolver can perform variable - /// expansion on manifests in the package before they're merged by aapt. - /// - /// true, if the aar should be exploded, false otherwise. - /// Path of the AAR file to query whether it should be exploded or - /// the path to the exploded AAR directory to determine whether the AAR should still be - /// exploded. - internal virtual bool ShouldExplode(string aarPath) { - bool newAarData = false; - AarExplodeData aarData = FindAarExplodeDataEntry(aarPath); - if (aarData == null) { - newAarData = true; - aarData = new AarExplodeData(); - aarData.path = aarPath; - } - string explodeDirectory = DetermineExplodedAarPath(aarPath); - bool explosionEnabled = true; - // Unfortunately, as of Unity 5.5.0f3, Unity does not set the applicationId variable - // in the build.gradle it generates. This results in non-functional libraries that - // require the ${applicationId} variable to be expanded in their AndroidManifest.xml. - // To work around this when Gradle builds are enabled, explosion is enabled for all - // AARs that require variable expansion unless this behavior is explicitly disabled - // in the settings dialog. - if (PlayServicesResolver.GradleProjectExportEnabled && !SettingsDialog.ExplodeAars) { - explosionEnabled = false; - } - AndroidAbis availableAbis = null; - bool explode = false; - if (explosionEnabled) { - explode = !SupportsAarFiles; - bool useCachedExplodeData = false; - bool aarFile = File.Exists(aarPath); - if (!explode) { - System.DateTime modificationTime = - aarFile ? File.GetLastWriteTime(aarPath) : System.DateTime.MinValue; - if (modificationTime.CompareTo(aarData.modificationTime) <= 0 && - !AarExplodeDataIsDirty(aarData)) { - explode = aarData.explode; - useCachedExplodeData = true; - } - } - if (!explode) { - // If the path is a directory then the caller is referencing an AAR that has - // already been exploded in which case we keep explosion enabled. - string aarDirectory = Directory.Exists(explodeDirectory) ? explodeDirectory : - Directory.Exists(aarData.path) ? aarData.path : null; - if (!String.IsNullOrEmpty(aarDirectory)) { - // If the directory contains native libraries update the target ABI. - if (!useCachedExplodeData) { - newAarData = true; - availableAbis = AarDirectoryFindAbis(aarDirectory); - } - explode = true; - } - } - if (!useCachedExplodeData && !explode) { - // If the file doesn't exist we can't interrogate it so we can assume it - // doesn't need to be exploded. - if (!aarFile) return false; - - string temporaryDirectory = FileUtils.CreateTemporaryDirectory(); - if (temporaryDirectory == null) return false; - try { - string manifestFilename = "AndroidManifest.xml"; - string classesFilename = "classes.jar"; - if (aarPath.EndsWith(".aar") && - PlayServicesResolver.ExtractZip( - aarPath, new string[] {manifestFilename, "jni", classesFilename}, - temporaryDirectory)) { - string manifestPath = Path.Combine(temporaryDirectory, - manifestFilename); - if (File.Exists(manifestPath)) { - string manifest = File.ReadAllText(manifestPath); - explode = manifest.IndexOf("${applicationId}") >= 0; - } - // If the AAR is badly formatted (e.g does not contain classes.jar) - // explode it so that we can create classes.jar. - explode |= !File.Exists(Path.Combine(temporaryDirectory, - classesFilename)); - // If the AAR contains more than one ABI and Unity's build is - // targeting a single ABI, explode it so that unused ABIs can be - // removed. - newAarData = true; - availableAbis = AarDirectoryFindAbis(temporaryDirectory); - // Unity 2017's internal build system does not support AARs that contain - // native libraries so force explosion to pick up native libraries using - // Eclipse / Ant style projects. - explode |= availableAbis != null && - Google.VersionHandler.GetUnityVersionMajorMinor() >= 2017.0f; - // NOTE: Unfortunately as of Unity 5.5 the internal Gradle build - // also blindly includes all ABIs from AARs included in the project - // so we need to explode the AARs and remove unused ABIs. - if (availableAbis != null) { - var abisToRemove = availableAbis.ToSet(); - abisToRemove.ExceptWith(AndroidAbis.Current.ToSet()); - explode |= abisToRemove.Count > 0; - } - aarData.modificationTime = File.GetLastWriteTime(aarPath); - } - } - catch (System.Exception e) { - PlayServicesResolver.Log( - String.Format("Unable to examine AAR file {0}\n\n{1}", aarPath, e), - level: LogLevel.Error); - throw e; - } - finally { - var deleteError = FileUtils.FormatError( - "Failed to clean up temporary directory", - FileUtils.DeleteExistingFileOrDirectory(temporaryDirectory)); - if (!String.IsNullOrEmpty(deleteError)) { - PlayServicesResolver.Log(deleteError, level: LogLevel.Warning); - } - } - } - } - // If this is a new cache entry populate the target ABI and bundle ID fields. - if (newAarData) { - aarData.AvailableAbis = availableAbis; - aarData.TargetAbis = AndroidAbis.Current; - aarData.bundleId = PlayServicesResolver.GetAndroidApplicationId(); - } - aarData.path = GenerateAntProject(explode) ? explodeDirectory : aarPath; - aarData.explode = explode; - aarExplodeData[AarPathToPackageName(aarPath)] = aarData; - SaveAarExplodeCache(); - return explode; - } - } -} diff --git a/source/PlayServicesResolver/src/SettingsDialog.cs b/source/PlayServicesResolver/src/SettingsDialog.cs deleted file mode 100644 index fabbfe11..00000000 --- a/source/PlayServicesResolver/src/SettingsDialog.cs +++ /dev/null @@ -1,414 +0,0 @@ -// -// Copyright (C) 2015 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace GooglePlayServices { - using System; - using System.IO; - using UnityEditor; - using UnityEngine; - using Google; - - /// - /// Settings dialog for PlayServices Resolver. - /// - public class SettingsDialog : EditorWindow { - /// - /// Loads / saves settings for this dialog. - /// - private class Settings { - internal bool enableAutoResolution; - internal bool autoResolveOnBuild; - internal bool useGradleDaemon; - internal bool installAndroidPackages; - internal string packageDir; - internal bool explodeAars; - internal bool patchAndroidManifest; - internal bool verboseLogging; - internal bool autoResolutionDisabledWarning; - internal bool promptBeforeAutoResolution; - internal bool useProjectSettings; - - /// - /// Load settings into the dialog. - /// - internal Settings() { - enableAutoResolution = SettingsDialog.EnableAutoResolution; - autoResolveOnBuild = SettingsDialog.AutoResolveOnBuild; - useGradleDaemon = SettingsDialog.UseGradleDaemon; - installAndroidPackages = SettingsDialog.InstallAndroidPackages; - packageDir = SettingsDialog.PackageDir; - explodeAars = SettingsDialog.ExplodeAars; - patchAndroidManifest = SettingsDialog.PatchAndroidManifest; - verboseLogging = SettingsDialog.VerboseLogging; - autoResolutionDisabledWarning = SettingsDialog.AutoResolutionDisabledWarning; - promptBeforeAutoResolution = SettingsDialog.PromptBeforeAutoResolution; - useProjectSettings = SettingsDialog.UseProjectSettings; - } - - /// - /// Save dialog settings to preferences. - /// - internal void Save() { - SettingsDialog.UseGradleDaemon = useGradleDaemon; - SettingsDialog.EnableAutoResolution = enableAutoResolution; - SettingsDialog.AutoResolveOnBuild = autoResolveOnBuild; - SettingsDialog.InstallAndroidPackages = installAndroidPackages; - if (SettingsDialog.ConfigurablePackageDir) SettingsDialog.PackageDir = packageDir; - SettingsDialog.ExplodeAars = explodeAars; - SettingsDialog.PatchAndroidManifest = patchAndroidManifest; - SettingsDialog.VerboseLogging = verboseLogging; - SettingsDialog.AutoResolutionDisabledWarning = autoResolutionDisabledWarning; - SettingsDialog.PromptBeforeAutoResolution = promptBeforeAutoResolution; - SettingsDialog.UseProjectSettings = useProjectSettings; - } - } - - const string Namespace = "GooglePlayServices."; - private const string AutoResolveKey = Namespace + "AutoResolverEnabled"; - private const string AutoResolveOnBuildKey = Namespace + "AutoResolveOnBuild"; - private const string PackageInstallKey = Namespace + "AndroidPackageInstallationEnabled"; - private const string PackageDirKey = Namespace + "PackageDirectory"; - private const string ExplodeAarsKey = Namespace + "ExplodeAars"; - private const string PatchAndroidManifestKey = Namespace + "PatchAndroidManifest"; - private const string VerboseLoggingKey = Namespace + "VerboseLogging"; - private const string AutoResolutionDisabledWarningKey = - Namespace + "AutoResolutionDisabledWarning"; - private const string PromptBeforeAutoResolutionKey = - Namespace + "PromptBeforeAutoResolution"; - private const string UseGradleDaemonKey = Namespace + "UseGradleDaemon"; - - // List of preference keys, used to restore default settings. - private static string[] PreferenceKeys = new[] { - AutoResolveKey, - AutoResolveOnBuildKey, - PackageInstallKey, - PackageDirKey, - ExplodeAarsKey, - PatchAndroidManifestKey, - VerboseLoggingKey, - AutoResolutionDisabledWarningKey, - PromptBeforeAutoResolutionKey, - UseGradleDaemonKey - }; - - internal const string AndroidPluginsDir = "Assets/Plugins/Android"; - - // Unfortunately, Unity currently does not search recursively search subdirectories of - // AndroidPluginsDir for Android library plugins. When this is supported - or we come up - // with a workaround - this can be enabled. - static bool ConfigurablePackageDir = false; - static string DefaultPackageDir = AndroidPluginsDir; - - private Settings settings; - - private static ProjectSettings projectSettings = new ProjectSettings(Namespace); - - // Previously validated package directory. - private static string previouslyValidatedPackageDir; - - /// - /// Reset settings of this plugin to default values. - /// - internal static void RestoreDefaultSettings() { - projectSettings.DeleteKeys(PreferenceKeys); - } - - internal static bool EnableAutoResolution { - set { - projectSettings.SetBool(AutoResolveKey, value); - if (value) { - PlayServicesResolver.LinkAutoResolution(); - } else { - PlayServicesResolver.UnlinkAutoResolution(); - } - } - get { return projectSettings.GetBool(AutoResolveKey, true); } - } - - internal static bool AutoResolveOnBuild { - set { - projectSettings.SetBool(AutoResolveOnBuildKey, value); - } - get { return projectSettings.GetBool(AutoResolveOnBuildKey, true); } - } - - internal static bool UseGradleDaemon { - private set { projectSettings.SetBool(UseGradleDaemonKey, value); } - get { return projectSettings.GetBool(UseGradleDaemonKey, false); } - } - - internal static bool InstallAndroidPackages { - private set { projectSettings.SetBool(PackageInstallKey, value); } - get { return projectSettings.GetBool(PackageInstallKey, true); } - } - - - internal static string PackageDir { - private set { projectSettings.SetString(PackageDirKey, value); } - get { - return ValidatePackageDir( - ConfigurablePackageDir ? - (projectSettings.GetString(PackageDirKey, DefaultPackageDir)) : - DefaultPackageDir); - } - } - - internal static bool AutoResolutionDisabledWarning { - set { projectSettings.SetBool(AutoResolutionDisabledWarningKey, value); } - get { return projectSettings.GetBool(AutoResolutionDisabledWarningKey, true); } - } - - /// - /// This setting is not exposed in the Settings menu but is - /// leveraged by the PlayServicesResolver to determine whether to - /// display a prompt. - /// - internal static bool PromptBeforeAutoResolution { - set { - projectSettings.SetBool(PromptBeforeAutoResolutionKey, value); - } - get { return projectSettings.GetBool(PromptBeforeAutoResolutionKey, true); } - } - - internal static bool UseProjectSettings { - get { return projectSettings.UseProjectSettings; } - set { projectSettings.UseProjectSettings = value; } - } - - // Whether AARs that use variable expansion should be exploded when Gradle builds are - // enabled. - internal static bool ExplodeAars { - set { projectSettings.SetBool(ExplodeAarsKey, value); } - get { return projectSettings.GetBool(ExplodeAarsKey, true); } - } - - internal static string AndroidManifestPath { - get { return Path.Combine(PackageDir, "AndroidManifest.xml"); } - } - - internal static bool PatchAndroidManifest { - set { projectSettings.SetBool(PatchAndroidManifestKey, value); } - get { return projectSettings.GetBool(PatchAndroidManifestKey, true); } - } - - internal static bool VerboseLogging { - private set { projectSettings.SetBool(VerboseLoggingKey, value); } - get { return projectSettings.GetBool(VerboseLoggingKey, false); } - } - - internal static string ValidatePackageDir(string directory) { - // Make sure the package directory starts with the same name. - // This is case insensitive to handle cases where developers rename Unity - // project directories on Windows (which has a case insensitive file system by - // default) then they use the project on OSX / Linux. - if (!directory.ToLowerInvariant().StartsWith(AndroidPluginsDir.ToLower())) { - directory = AndroidPluginsDir; - } - directory = FileUtils.NormalizePathSeparators(directory); - var searchDirectory = FileUtils.FindDirectoryByCaseInsensitivePath(directory); - if (String.IsNullOrEmpty(searchDirectory)) searchDirectory = directory; - if (directory != searchDirectory && - (previouslyValidatedPackageDir == null || - searchDirectory != previouslyValidatedPackageDir)) { - PlayServicesResolver.Log( - String.Format("Resolving to Android package directory {0} instead of the " + - "requested target directory {1}\n" + - "\n" + - "Is {0} in a different case to {1} ?\n", - searchDirectory, directory), level: LogLevel.Warning); - directory = searchDirectory; - } else if ((previouslyValidatedPackageDir == null || - searchDirectory != previouslyValidatedPackageDir) && - searchDirectory == null) { - PlayServicesResolver.Log( - String.Format("Android package directory {0} not found.", - directory), level: LogLevel.Warning); - } - previouslyValidatedPackageDir = searchDirectory; - - return directory; - } - - public void Initialize() { - minSize = new Vector2(425, 455); - position = new Rect(UnityEngine.Screen.width / 3, UnityEngine.Screen.height / 3, - minSize.x, minSize.y); - } - - public void LoadSettings() { - settings = new Settings(); - } - - public void OnEnable() { - LoadSettings(); - } - - /// - /// Called when the GUI should be rendered. - /// - public void OnGUI() { - GUI.skin.label.wordWrap = true; - GUILayout.BeginVertical(); - GUILayout.Label(String.Format("Android Resolver (version {0}.{1}.{2})", - AndroidResolverVersionNumber.Value.Major, - AndroidResolverVersionNumber.Value.Minor, - AndroidResolverVersionNumber.Value.Build)); - - GUILayout.BeginHorizontal(); - GUILayout.Label("Use Gradle Daemon", EditorStyles.boldLabel); - settings.useGradleDaemon = EditorGUILayout.Toggle(settings.useGradleDaemon); - GUILayout.EndHorizontal(); - GUILayout.Label( - settings.useGradleDaemon ? - ("Gradle Daemon will be used to fetch dependencies. " + - "This is faster but can be flakey in some environments.") : - ("Gradle Daemon will not be used. This is slow but reliable.")); - - GUILayout.BeginHorizontal(); - GUILayout.Label("Enable Auto-Resolution", EditorStyles.boldLabel); - settings.enableAutoResolution = EditorGUILayout.Toggle(settings.enableAutoResolution); - GUILayout.EndHorizontal(); - GUILayout.Label( - settings.enableAutoResolution ? - ("Android libraries will be downloaded and processed in the editor.") : - ("Android libraries will *not* be downloaded or processed in the editor.")); - - GUILayout.BeginHorizontal(); - GUILayout.Label("Enable Resolution On Build", EditorStyles.boldLabel); - settings.autoResolveOnBuild = EditorGUILayout.Toggle(settings.autoResolveOnBuild); - GUILayout.EndHorizontal(); - GUILayout.Label( - settings.autoResolveOnBuild ? - ("Android libraries will be downloaded and processed in a pre-build step.") : - ("Android libraries will *not* be downloaded or processed in a pre-build step.")); - - GUILayout.BeginHorizontal(); - GUILayout.Label("Install Android Packages", EditorStyles.boldLabel); - settings.installAndroidPackages = - EditorGUILayout.Toggle(settings.installAndroidPackages); - GUILayout.EndHorizontal(); - - if (ConfigurablePackageDir) { - GUILayout.BeginHorizontal(); - string previousPackageDir = settings.packageDir; - GUILayout.Label("Package Directory", EditorStyles.boldLabel); - if (GUILayout.Button("Browse")) { - string path = EditorUtility.OpenFolderPanel("Set Package Directory", - PackageDir, ""); - int startOfPath = path.IndexOf(AndroidPluginsDir); - settings.packageDir = startOfPath < 0 ? "" : - path.Substring(startOfPath, path.Length - startOfPath);; - } - if (!previousPackageDir.Equals(settings.packageDir)) { - settings.packageDir = ValidatePackageDir(settings.packageDir); - } - GUILayout.EndHorizontal(); - settings.packageDir = EditorGUILayout.TextField(settings.packageDir); - } - - GUILayout.BeginHorizontal(); - GUILayout.Label("Explode AARs", EditorStyles.boldLabel); - settings.explodeAars = EditorGUILayout.Toggle(settings.explodeAars); - GUILayout.EndHorizontal(); - if (settings.explodeAars) { - GUILayout.Label("AARs will be exploded (unpacked) when ${applicationId} " + - "variable replacement is required in an AAR's " + - "AndroidManifest.xml or a single target ABI is selected " + - "without a compatible build system."); - } else { - GUILayout.Label("AAR explosion will be disabled in exported Gradle builds " + - "(Unity 5.5 and above). You will need to set " + - "android.defaultConfig.applicationId to your bundle ID in your " + - "build.gradle to generate a functional APK."); - } - - // Disable the ability to toggle the auto-resolution disabled warning - // when auto resolution is enabled. - EditorGUI.BeginDisabledGroup(settings.enableAutoResolution || - settings.autoResolveOnBuild); - GUILayout.BeginHorizontal(); - GUILayout.Label("Auto-Resolution Disabled Warning", EditorStyles.boldLabel); - settings.autoResolutionDisabledWarning = - EditorGUILayout.Toggle(settings.autoResolutionDisabledWarning); - GUILayout.EndHorizontal(); - EditorGUI.EndDisabledGroup(); - - // Disable the ability to toggle the auto-resolution disabled warning - // when auto resolution is enabled. - EditorGUI.BeginDisabledGroup(!settings.enableAutoResolution); - GUILayout.BeginHorizontal(); - GUILayout.Label("Prompt Before Auto-Resolution", EditorStyles.boldLabel); - settings.promptBeforeAutoResolution = - EditorGUILayout.Toggle(settings.promptBeforeAutoResolution); - GUILayout.EndHorizontal(); - EditorGUI.EndDisabledGroup(); - - GUILayout.BeginHorizontal(); - GUILayout.Label("Patch AndroidManifest.xml", EditorStyles.boldLabel); - settings.patchAndroidManifest = EditorGUILayout.Toggle(settings.patchAndroidManifest); - GUILayout.EndHorizontal(); - if (settings.patchAndroidManifest) { - GUILayout.Label(String.Format( - "Instances of \"applicationId\" variable references will be replaced in " + - "{0} with the bundle ID. If the bundle ID " + - "is changed the previous bundle ID will be replaced with the new " + - "bundle ID by the plugin.\n\n" + - "This works around a bug in Unity 2018.x where the " + - "\"applicationId\" variable is not replaced correctly.", - AndroidManifestPath)); - } else { - GUILayout.Label(String.Format( - "{0} is not modified.\n\n" + - "If you're using Unity 2018.x and have an AndroidManifest.xml " + - "that uses the \"applicationId\" variable, your build may fail.", - AndroidManifestPath)); - } - - GUILayout.BeginHorizontal(); - GUILayout.Label("Verbose Logging", EditorStyles.boldLabel); - settings.verboseLogging = EditorGUILayout.Toggle(settings.verboseLogging); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GUILayout.Label("Use project settings", EditorStyles.boldLabel); - settings.useProjectSettings = EditorGUILayout.Toggle(settings.useProjectSettings); - GUILayout.EndHorizontal(); - - GUILayout.Space(10); - - if (GUILayout.Button("Reset to Defaults")) { - // Load default settings into the dialog but preserve the state in the user's - // saved preferences. - var backupSettings = new Settings(); - RestoreDefaultSettings(); - LoadSettings(); - backupSettings.Save(); - } - - GUILayout.BeginHorizontal(); - bool closeWindow = GUILayout.Button("Cancel"); - bool ok = GUILayout.Button("OK"); - closeWindow |= ok; - if (ok) { - settings.Save(); - PlayServicesResolver.OnSettingsChanged(); - } - if (closeWindow) Close(); - GUILayout.EndHorizontal(); - GUILayout.EndVertical(); - } - } -} diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.google.firebase.firebase-app-unity-5.1.1.aar deleted file mode 100644 index 19dba6da..00000000 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.google.firebase.firebase-app-unity-5.1.1.aar and /dev/null differ diff --git a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.google.firebase.firebase-app-unity-5.1.1.aar b/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.google.firebase.firebase-app-unity-5.1.1.aar deleted file mode 100644 index dde15322..00000000 Binary files a/source/PlayServicesResolver/test/resolve_async/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.google.firebase.firebase-app-unity-5.1.1.aar and /dev/null differ diff --git a/source/UnityAssetUploader/.gitignore b/source/UnityAssetUploader/.gitignore new file mode 100644 index 00000000..c18dd8d8 --- /dev/null +++ b/source/UnityAssetUploader/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/source/UnityAssetUploader/README.md b/source/UnityAssetUploader/README.md new file mode 100644 index 00000000..0c14cff9 --- /dev/null +++ b/source/UnityAssetUploader/README.md @@ -0,0 +1,42 @@ +# Unity Asset Uploader + +Command-line interface to authenticate with the unity asset store +servers, get information about publisher accounts, and upload new asset +packages. + +## Requirements + +* Python 3 +* A Unity Asset Store [publisher account](https://publisher.assetstore.unity3d.com). + +## Usage +``` +positional arguments: + {display_session_id,display_publisher_info,display_listings,upload_package} + display_session_id Print (possibly refreshed) auth session ID. + display_publisher_info + Print publisher ID and name. + display_listings Print information about each package listed under + publisher account. + upload_package Upload a local unitypackage file to unity asset store. + Args: + package_id: Package ID to upload, retrieved from get_display_listings. + package_path: Path to source, retrieved from get_display_listings. + +global arguments: + -h, --help show this help message and exit + --username USERNAME Username of the Unity publisher account. With --password, one of two ways to + authenticate requests. + Defaults to environment variable UNITY_USERNAME. + --password PASSWORD Password of the Unity publisher account. With --username, one of two ways to + authenticate requests. + Defaults to environment variable UNITY_PASSWORD. + --session_id SESSION_ID + Session ID / auth key returned from display_session_id. Option 2 for + authenticating requests. + --server SERVER Server component of the asset store API URL. + --unity_version UNITY_VERSION + Version of Unity to report to the asset store. + --tools_version TOOLS_VERSION + Version of Tools plugin to report to the asset store. +``` diff --git a/source/UnityAssetUploader/unity_asset_uploader.py b/source/UnityAssetUploader/unity_asset_uploader.py new file mode 100644 index 00000000..b1ebfb8e --- /dev/null +++ b/source/UnityAssetUploader/unity_asset_uploader.py @@ -0,0 +1,583 @@ +#!/usr/bin/python +# +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Command-line interface to authenticate with the unity asset store +servers, get information about publisher accounts, and upload new asset +packages. +""" + +import argparse +from http import client +from urllib import parse +import json +import os +import sys + +DEFAULT_TOOLS_VERSION = 'v4.1.0' +DEFAULT_UNITY_VERSION = '5.6.0f3' + +_DEFAULT_SERVER = 'kharma.unity3d.com' +LISTING_TRAITS = [ + 'name', + 'version_name', + 'icon_url', + 'preview_url', + 'root_guid', + 'status'] + +_HTTP_METHOD_GET = 1 +_HTTP_METHOD_POST = 2 +_HTTP_METHOD_PUT = 3 + + +class InvalidRequestError(Exception): + """Raised when the required parameters are not provided for a request.""" + pass + + +class RequestError(Exception): + """Raised when a request to the asset store fails or does not return + expected output.""" + pass + + +class AssetStoreSession(object): + """Stores data about a unity asset store session, including login info, + server, and store/tool version. + """ + + def __init__( + self, + username=None, + password=None, + session_id=None, + server=_DEFAULT_SERVER, + unity_version=DEFAULT_UNITY_VERSION, + tools_version=DEFAULT_TOOLS_VERSION): + """Create an instance of AssetStoreSession. + Args: + username: With password, one option for authenticating request. + password: With username, one option for authenticating request. + session_id: Option 2 for authenticating request. + server: The http server to which to direct requests. + unity_version: Version of unity used to include with request. + tools_version: Version of Asset Store Tools to include. + """ + self.username = username + self.password = password + self.session_id = session_id + self.session_id = None + self.server = server + self.unity_version = unity_version + self.tools_version = tools_version + + def _encode_path_and_params(self, path, params={}): + """Encodes the path string with the params dict as query string. + + Args: + path: Path string for request, e.g. /login. + params: Parameters for request. + + Returns: + Encoded path+query string, e.g. /login?username=User_1... + """ + encoded_params = parse.urlencode(params) + return "{}?{}".format(path, encoded_params) + + def _make_request( + self, + path, + method=_HTTP_METHOD_GET, + params=None, + headers=None, + body=None): + """ Places an https request and returns the decoded JSON response object. + + Args: + path: Path portion of url to retrieve, + e.g. "/api/asset-store-tools/metadata/0.json" + method: Http method to use as static flag above, + e.g. _HTTP_METHOD_GET. + params: Any additional params to include with request. + headers: Any additional headers to include with request. + body: Form body for PUT requests. + + Returns: + JSON-decoded response data. + + Raises: + RequestFailedError if response is not found. + """ + params = dict(params) if params else {} + headers = dict(headers) if headers else {} + + if not self.session_id: + self._login() + + if self.username: + params['user'] = self.username + if self.password: + params['pass'] = self.password + params['unityversion'] = self.unity_version + params['toolversion'] = self.tools_version + params['xunitysession'] = self.session_id + + headers['Accept'] = 'application/json' + + encoded_params = parse.urlencode(params) + + try: + connection = client.HTTPSConnection(self.server) + if method == _HTTP_METHOD_GET: + connection.request( + 'GET', + self._encode_path_and_params(path, params), + headers=headers) + elif method == _HTTP_METHOD_POST: + headers['Content-Type'] = 'application/x-www-form-urlencoded' + connection.request( + 'POST', + path, + parse.urlencode(params), + headers=headers) + elif method == _HTTP_METHOD_PUT: + connection.request( + 'PUT', + self._encode_path_and_params(path, params), + body, + headers=headers) + else: + raise ValueError("Invalid http method provided.") + response = connection.getresponse() + if response.status > client.FOUND: + print("Response: {}".format(response.read())) + raise Exception("Error making http request: {} {}".format( + response.status, response.reason)) + + response_ob = json.load(response) + finally: + connection.close() + + return response_ob + + def _login(self): + """ Places an https request to /login with either username/password or session_id. + + Returns: + JSON-decoded response data. + + Raises: + RequestFailedError if response is not found. + """ + params = {} + + if self.username and self.password: + params['user'] = self.username + params['pass'] = self.password + elif self.session_id: + params['xunitysession'] = self.session_id + else: + raise InvalidRequestError( + 'Either username and password or session_id is required.') + params['unityversion'] = self.unity_version + params['toolversion'] = self.tools_version + + try: + connection = client.HTTPSConnection(self.server) + encoded_path = self._encode_path_and_params("/login", params) + connection.request( + 'GET', + encoded_path, + headers={'Accept': 'application/json'}) + response = connection.getresponse() + if response.status > client.FOUND: + print("Response: {}".format(response.read())) + raise RequestError("Error making https request: {} {}".format( + response.status, response.reason)) + + response_ob = json.load(response) + self.session_id = response_ob['xunitysession'] + if not self.session_id: + raise RequestError( + 'Unable to login to unity asset store server.') + finally: + connection.close() + + return response_ob + + def get_session_id(self): + """ Retrieve session ID for non-login calls that provide user and pass. + + Returns: + Unity store session ID as string. + """ + response_ob = self._login() + if not self.session_id: + raise RequestError('No xunitysession found in Http response.') + return self.session_id + + def get_metadata(self): + """ Get publisher/package metadata from + /api/asset-store-tools/metadata/0.json. + + Metadata contains JSON-encoded information about the publisher and + their packages on the asset store, including ID, project + path/version, draft status, icon assets, and more. + + Returns: + JSON-formatted metadata. + """ + return self._make_request('/api/asset-store-tools/metadata/0.json') + + def get_publisher_info(self): + response_ob = self.get_metadata() + return response_ob['publisher'] + + def get_display_listings(self): + response_ob = self.get_metadata() + return response_ob['packages'] + + def upload_package(self, package_id, package_path): + metadata = self.get_metadata() + + if package_id not in metadata['packages']: + raise InvalidRequestError( + 'Error: could not find package with version ID {}'.format( + package_id)) + package = metadata['packages'][package_id] + + # If package is not in "draft" state, a new draft must be created by calling + # the publisher assetstore endpoint. + if package['status'] != 'draft': + raise InvalidRequestError( + "Error: no draft created for package {}. ".format(package_id) + + "Please create a draft via " + + "https://publisher.assetstore.unity3d.com/") + + path = "/api/asset-store-tools/package/{}/unitypackage.json".format( + package['id']) + params = { + 'xunitysession': self.session_id, + 'root_guid': package['root_guid'], + 'root_path': package['root_path'], + 'project_path': package['project_path']} + + try: + with open(package_path, 'rb') as package_file: + response_ob = self._make_request( + path, + method=_HTTP_METHOD_PUT, + params=params, + body=package_file.read()) + + package_file.close() + return response_ob + except Exception as ex: + raise RequestError('Exception while processing package upload.') + + +def get_session_id( + username, + password, + session_id, + server=_DEFAULT_SERVER, + unity_version=DEFAULT_UNITY_VERSION, + tools_version=DEFAULT_TOOLS_VERSION): + """Retrieve xunitysession for non-login calls that provide user and pass. + + Args: + username: With password, one option for authenticating request. + password: With username, one option for authenticating request. + session_id: Option 2 for authenticating request. If provided, simply + returns this value. + server: The http server to which to direct requests. + unity_version: Version of unity used to include with request. + tools_version: Version of Asset Store Tools to include with request. + + Returns: + Unity store session ID as string. + """ + session = AssetStoreSession( + server=server, + username=username, + password=password, + session_id=session_id, + unity_version=unity_version, + tools_version=tools_version) + return session.get_session_id() + + +def display_session_id( + username, + password, + session_id, + server=_DEFAULT_SERVER, + unity_version=DEFAULT_UNITY_VERSION, + tools_version=DEFAULT_TOOLS_VERSION): + """Print auth session ID. Requires username and password args. + + Args: + username: With password, one option for authenticating request. + password: With username, one option for authenticating request. + session_id: Option 2 for authenticating request. + server: The http server to which to direct requests. + unity_version: Version of unity used to include with request. + tools_version: Version of Asset Store Tools to include with request. + """ + session = AssetStoreSession( + username=username, + password=password, + session_id=session_id, + server=server, + unity_version=unity_version, + tools_version=tools_version) + print("session_id={}".format(session.get_session_id())) + + +def display_publisher_info( + username, + password, + session_id, + server=_DEFAULT_SERVER, + unity_version=DEFAULT_UNITY_VERSION, + tools_version=DEFAULT_TOOLS_VERSION): + """Print publisher ID and name. + + Args: + username: With password, one option for authenticating request. + password: With username, one option for authenticating request. + session_id: Option 2 for authenticating request. + server: The http server to which to direct requests. + unity_version: Version of unity used to include with request. + tools_version: Version of Asset Store Tools to include with request. + """ + session = AssetStoreSession( + username=username, + password=password, + session_id=session_id, + server=server, + unity_version=unity_version, + tools_version=tools_version) + metadta = session.get_metadata() + print("publisher_id={}; publisher_name={}; package_ids=({})".format( + metadta['publisher']['id'], + metadta['publisher']['name'], + ' '.join(metadta['packages'].keys()))) + + +def display_listings( + username, + password, + session_id, + server=_DEFAULT_SERVER, + unity_version=DEFAULT_UNITY_VERSION, + tools_version=DEFAULT_TOOLS_VERSION): + """Print information about each package listed under publisher account. + + Args: + username: With password, one option for authenticating request. + password: With username, one option for authenticating request. + session_id: Option 2 for authenticating request. + server: The http server to which to direct requests. + unity_version: Version of unity used to include with request. + tools_version: Version of Asset Store Tools to include with request. + """ + session = AssetStoreSession( + username=username, + password=password, + session_id=session_id, + server=server, + unity_version=unity_version, + tools_version=tools_version) + metadata = session.get_metadata() + packages = metadata['packages'] + for package_id in packages: + package = packages[package_id] + output = [ + "package_id={}".format(package_id), + "package_version_id={}".format(package['id']) + ] + for trait in LISTING_TRAITS: + output.append("{}={}".format(trait, package[trait])) + print('; '.join(output)) + + +def upload_package( + username, + password, + session_id, + package_id, + package_path, + server=_DEFAULT_SERVER, + unity_version=DEFAULT_UNITY_VERSION, + tools_version=DEFAULT_TOOLS_VERSION): + """Upload a local unitypackage file to unity asset store. + + Args: + username: With password, one option for authenticating request. + password: With username, one option for authenticating request. + session_id: Option 2 for authenticating request. + package_id: ID of package to upload, as retrieved from + session.get_display_listings(). + package_path: Path to package source, as retrieved from + session.get_display_listings(). + server: The http server to which to direct requests. + unity_version: Version of unity used to include with request. + tools_version: Version of Asset Store Tools to include with request. + """ + session = AssetStoreSession( + username=username, + password=password, + session_id=session_id, + server=server, + unity_version=unity_version, + tools_version=tools_version) + response_ob = session.upload_package(package_id, package_path) + status = response_ob.get('status', '') + print("status={}".format(status)) + if status != 'ok': + raise RequestError( + 'Non-success response from Asset Store request: {}'.format( + status)) + print(response_ob) + + +def parse_commandline_args(): + """Parses command line arguments.""" + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + + # Auth arguments. Either username+password or session_id is required. + parser.add_argument( + '--username', + default=os.environ.get('UNITY_USERNAME'), + help='Username of the Unity publisher account.') + + parser.add_argument( + '--password', + default=os.environ.get('UNITY_PASSWORD'), + help='Password of the Unity publisher account.') + + parser.add_argument( + '--session_id', + default=None, + help='Session ID / auth key returned from display_session_id.') + + parser.add_argument( + '--server', + default=_DEFAULT_SERVER, + help='Server component of the asset store API URL.') + + # Miscellaneous args. + parser.add_argument( + '--unity_version', + default=DEFAULT_UNITY_VERSION, + help='Version of Unity to report to the asset store.') + + parser.add_argument( + '--tools_version', + default=DEFAULT_TOOLS_VERSION, + help='Version of Tools plugin to report to the asset store.') + + # Command subparsers + command = parser.add_subparsers(dest='command') + + command.add_parser( + 'display_session_id', + help=display_session_id.__doc__) + + command.add_parser( + 'display_publisher_info', + help=display_publisher_info.__doc__) + + command.add_parser( + 'display_listings', + help=display_listings.__doc__) + + upload_package_command = command.add_parser( + 'upload_package', + help=upload_package.__doc__) + + upload_package_command.add_argument( + '--package_id', + help='Package ID of the package to upload from package_path.', + default=os.environ.get('UNITY_PACKAGE_ID')) + + upload_package_command.add_argument( + '--package_path', + help='Path to the .unitypackage file to upload.', + default=os.environ.get('UNITY_PACKAGE_PATH')) + + # Verify either username+password or session_id is provided. + args = parser.parse_args() + if (args.session_id is None and ( + args.username is None or args.password is None)): + sys.stderr.write( + 'Either --session_id or --username and --password required.') + parser.print_help() + return 1 + return args + + +def run_command(args): + + if args.command == 'display_session_id': + display_session_id( + args.username, + args.password, + args.session_id, + args.server, + args.unity_version, + args.tools_version) + + elif args.command == 'display_publisher_info': + display_publisher_info( + args.username, + args.password, + args.session_id, + args.server, + args.unity_version, + args.tools_version) + + elif args.command == 'display_listings': + display_listings( + args.username, + args.password, + args.session_id, + args.server, + args.unity_version, + args.tools_version) + + elif args.command == 'upload_package': + upload_package( + args.username, + args.password, + args.session_id, + args.package_id, + args.package_path, + args.server, + args.unity_version, + args.tools_version) + + +def main(): + args = parse_commandline_args() + return run_command(args) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/source/UnityAssetUploader/unity_asset_uploader_integration_test.py b/source/UnityAssetUploader/unity_asset_uploader_integration_test.py new file mode 100644 index 00000000..1ac3e254 --- /dev/null +++ b/source/UnityAssetUploader/unity_asset_uploader_integration_test.py @@ -0,0 +1,80 @@ +#!/usr/bin/python +# +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from io import StringIO +import os +import sys +import unittest +from unittest.mock import patch + +import unity_asset_uploader + + +unity_username = os.environ.get('UNITY_USERNAME') +unity_password = os.environ.get('UNITY_PASSWORD') +unity_package_id = os.environ.get('UNITY_PACKAGE_ID') +unity_package_path = os.environ. get('UNITY_PACKAGE_PATH') + + +class TestUploaderMethods(unittest.TestCase): + + @patch('unity_asset_uploader.sys.stdout', new_callable=StringIO) + def test_display_session_id(self, mock_stdout): + unity_asset_uploader.display_session_id( + unity_username, + unity_password, + None) + + assert 'session_id' in mock_stdout.getvalue() + + @patch('unity_asset_uploader.sys.stdout', new_callable=StringIO) + def test_display_publisher_info(self, mock_stdout): + unity_asset_uploader.display_publisher_info( + unity_username, + unity_password, + None) + + out = mock_stdout.getvalue().strip() + assert 'publisher_id' in out + assert 'publisher_name' in out + assert 'package_ids' in out + + @patch('unity_asset_uploader.sys.stdout', new_callable=StringIO) + def test_display_listings(self, mock_stdout): + unity_asset_uploader.display_listings( + unity_username, + unity_password, + None) + + out = mock_stdout.getvalue().strip() + assert 'package_id' in out + assert 'package_version_id' in out + + @patch('unity_asset_uploader.sys.stdout', new_callable=StringIO) + def test_upload_package(self, mock_stdout): + unity_asset_uploader.upload_package( + unity_username, + unity_password, + None, + unity_package_id, + unity_package_path) + + out = mock_stdout.getvalue().strip() + assert "{'status': 'ok'}" in out + + +if __name__ == '__main__': + unittest.main() diff --git a/source/UnityAssetUploader/unity_asset_uploader_test.py b/source/UnityAssetUploader/unity_asset_uploader_test.py new file mode 100644 index 00000000..7f1bde03 --- /dev/null +++ b/source/UnityAssetUploader/unity_asset_uploader_test.py @@ -0,0 +1,299 @@ +#!/usr/bin/python +# +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from http import client, HTTPStatus +from io import StringIO +import os +import sys +import unittest +from unittest.mock import call, patch, MagicMock +from urllib import parse + +import unity_asset_uploader + + +unity_username = 'UNITY_USERNAME' +unity_password = 'UNITY_PASSWORD' + + +class TestUploaderMethods(unittest.TestCase): + + def setUp(self): + self.parsed_params = parse.urlencode({ + 'user': unity_username, + 'pass': unity_password, + 'unityversion': unity_asset_uploader.DEFAULT_UNITY_VERSION, + 'toolversion': unity_asset_uploader.DEFAULT_TOOLS_VERSION, + }) + + self.expected_login_call = call( + 'GET', + "/login?{}".format(self.parsed_params), + headers={'Accept': 'application/json'}) + + self.expected_login_and_metadata_calls = [ + call( + 'GET', + "/login?{}".format(self.parsed_params), + headers={'Accept': 'application/json'}), + call('GET', + ("/api/asset-store-tools/metadata/0.json?{}" + + "&xunitysession=this_session_id").format( + self.parsed_params), + headers={'Accept': 'application/json'}), + ] + + self.expected_metadata = { + 'status': 'ok', + 'xunitysession': 'this_session_id', + 'publisher': { + 'id': 'publisher_id', + 'name': 'publisher_name' + }, + 'packages': { + '123': { + 'id': 'version_123', + 'name': 'package_one', + 'version_name': '1.0.0', + 'icon_url': 'https://example.com/icon', + 'preview_url': 'https://example.com/image', + 'project_path': '/mock_project', + 'root_guid': '1234567890', + 'root_path': '/mock_project/mock_path.unityasset', + 'status': 'draft', + }, + '456': { + 'id': 'version_456', + 'name': 'package_two', + 'version_name': '1.1.2', + 'icon_url': 'https://example.com/icon2', + 'preview_url': 'https://example.com/image2', + 'project_path': '/mock_project', + 'root_guid': '0987654321', + 'root_path': '/mock_project/mock_path2.unityasset', + 'status': 'published', + } + } + } + + @patch('unity_asset_uploader.json') + @patch('unity_asset_uploader.client.HTTPSConnection') + @patch('unity_asset_uploader.sys.stdout', new_callable=StringIO) + def test_display_session_id(self, mock_stdout, mock_connection, mock_json): + getresponse = mock_connection.return_value.getresponse.return_value + getresponse.status = HTTPStatus.OK + mock_json.load.return_value = self.expected_metadata + + unity_asset_uploader.display_session_id( + unity_username, + unity_password, + None) + + mock_connection.return_value.request.assert_called_with( + 'GET', "/login?{}".format(self.parsed_params), headers={ + 'Accept': 'application/json'}) + + mock_json.load.assert_called_with( + mock_connection.return_value.getresponse.return_value) + self.assertIn('this_session_id', mock_stdout.getvalue()) + + @patch('unity_asset_uploader.json') + @patch('unity_asset_uploader.client.HTTPSConnection') + @patch('unity_asset_uploader.sys.stdout', new_callable=StringIO) + def test_get_metadata(self, mock_stdout, mock_connection, mock_json): + getresponse = mock_connection.return_value.getresponse.return_value + getresponse.status = HTTPStatus.OK + mock_json.load.return_value = self.expected_metadata + + session = unity_asset_uploader.AssetStoreSession( + username=unity_username, + password=unity_password) + + metadata = session.get_metadata() + + request = mock_connection.return_value.request + self.assertEqual(self.expected_login_and_metadata_calls, + request.call_args_list) + + mock_json.load.assert_called_with( + mock_connection.return_value.getresponse.return_value) + self.assertEqual(self.expected_metadata, metadata) + + @patch('unity_asset_uploader.json') + @patch('unity_asset_uploader.client.HTTPSConnection') + @patch('unity_asset_uploader.sys.stdout', new_callable=StringIO) + def test_display_publisher_info( + self, mock_stdout, mock_connection, mock_json): + getresponse = mock_connection.return_value.getresponse.return_value + getresponse.status = HTTPStatus.OK + mock_json.load.return_value = self.expected_metadata + + unity_asset_uploader.display_publisher_info( + unity_username, + unity_password, + None) + + request = mock_connection.return_value.request + self.assertEqual(self.expected_login_and_metadata_calls, + request.call_args_list) + + mock_json.load.assert_called_with( + mock_connection.return_value.getresponse.return_value) + + out = mock_stdout.getvalue() + self.assertIn('publisher_id', out) + self.assertIn('publisher_name', out) + self.assertIn('123', out) + self.assertIn('456', out) + + @patch('unity_asset_uploader.json') + @patch('unity_asset_uploader.client.HTTPSConnection') + @patch('unity_asset_uploader.sys.stdout', new_callable=StringIO) + def test_display_listings(self, mock_stdout, mock_connection, mock_json): + getresponse = mock_connection.return_value.getresponse.return_value + getresponse.status = HTTPStatus.OK + mock_json.load.return_value = self.expected_metadata + + unity_asset_uploader.display_listings( + unity_username, + unity_password, + None) + + request = mock_connection.return_value.request + self.assertEqual(self.expected_login_and_metadata_calls, + request.call_args_list) + + mock_json.load.assert_called_with( + mock_connection.return_value.getresponse.return_value) + + out = mock_stdout.getvalue() + expected_456 = self.expected_metadata['packages']['456'] + package_two_values = expected_456.keys + for key in unity_asset_uploader.LISTING_TRAITS: + self.assertIn("{}={}".format(key, expected_456[key]), out) + + @patch('unity_asset_uploader.open') + @patch('unity_asset_uploader.json') + @patch('unity_asset_uploader.client.HTTPSConnection') + @patch('unity_asset_uploader.sys.stdout', new_callable=StringIO) + def test_upload_package_draft( + self, mock_stdout, mock_connection, mock_json, mock_open): + getresponse = mock_connection.return_value.getresponse.return_value + getresponse.status = HTTPStatus.OK + mock_json.load.return_value = self.expected_metadata + mock_file = MagicMock() + read = mock_open.return_value.__enter__.return_value.read + read.return_value = 'encoded_package_body' + + unity_asset_uploader.upload_package( + unity_username, + unity_password, + None, + '123', + '/mock_project/mock_path.unityasset') + + expected_123 = self.expected_metadata['packages']['123'] + upload_params = parse.urlencode({ + 'xunitysession': 'this_session_id', + 'root_guid': expected_123['root_guid'], + 'root_path': expected_123['root_path'], + 'project_path': expected_123['project_path'], + }) + + parsed_params = "{}&{}".format(upload_params, self.parsed_params) + + pat = "/api/asset-store-tools/package/version_123/unitypackage.json?{}" + + expected_call_args = list.copy(self.expected_login_and_metadata_calls) + expected_call_args.append(call( + 'PUT', + pat.format(parsed_params), + 'encoded_package_body', + headers={'Accept': 'application/json'})) + + request = mock_connection.return_value.request + self.assertEqual(expected_call_args, request.call_args_list) + + mock_json.load.assert_called_with( + mock_connection.return_value.getresponse.return_value) + mock_open.assert_called_with( + '/mock_project/mock_path.unityasset', 'rb') + + @patch('unity_asset_uploader.open') + @patch('unity_asset_uploader.json') + @patch('unity_asset_uploader.client.HTTPSConnection') + @patch('unity_asset_uploader.sys.stdout', new_callable=StringIO) + def test_upload_package_published_error( + self, mock_stdout, mock_connection, mock_json, mock_open): + getresponse = mock_connection.return_value.getresponse.return_value + getresponse.status = HTTPStatus.OK + mock_json.load.return_value = self.expected_metadata + mock_file = MagicMock() + read = mock_open.return_value.__enter__.return_value.read + read.return_value = 'encoded_package_body' + + with self.assertRaises(unity_asset_uploader.InvalidRequestError) as cm: + unity_asset_uploader.upload_package( + unity_username, + unity_password, + None, + '456', + '/mock_project2/mock_path.unityasset') + + self.assertIn('no draft created for package 456', cm.exception.output) + + request = mock_connection.return_value.request + self.assertEqual(self.expected_login_and_metadata_calls, + request.call_args_list) + + mock_json.load.assert_called_with( + mock_connection.return_value.getresponse.return_value) + mock_open.assert_not_called() + + @patch('unity_asset_uploader.open') + @patch('unity_asset_uploader.json') + @patch('unity_asset_uploader.client.HTTPSConnection') + @patch('unity_asset_uploader.sys.stdout', new_callable=StringIO) + def test_upload_package_not_found_error( + self, mock_stdout, mock_connection, mock_json, mock_open): + getresponse = mock_connection.return_value.getresponse.return_value + getresponse.status = HTTPStatus.OK + mock_json.load.return_value = self.expected_metadata + mock_file = MagicMock() + read = mock_open.return_value.__enter__.return_value.read + read.return_value = 'encoded_package_body' + + with self.assertRaises(unity_asset_uploader.InvalidRequestError) as cm: + unity_asset_uploader.upload_package( + unity_username, + unity_password, + None, + '789', + '/mock_project3/mock_path.unityasset') + error_msg = 'could not find package with version ID 789' + self.assertIn(error_msg, cm.exception.output) + + request = mock_connection.return_value.request + self.assertEqual(self.expected_login_and_metadata_calls, + request.call_args_list) + + mock_json.load.assert_called_with( + mock_connection.return_value.getresponse.return_value) + mock_open.assert_not_called() + + +if __name__ == '__main__': + unittest.main() diff --git a/source/VersionHandler/src/VersionHandler.cs b/source/VersionHandler/src/VersionHandler.cs index dae0eeb6..0b0f719e 100644 --- a/source/VersionHandler/src/VersionHandler.cs +++ b/source/VersionHandler/src/VersionHandler.cs @@ -112,12 +112,18 @@ private static bool BootStrapping { /// static VersionHandler() { // Schedule the process if the version handler isn't disabled on the command line. - if (System.Environment.CommandLine.Contains("-gvh_disable")) { + if (System.Environment.CommandLine.ToLower().Contains("-gvh_disable")) { UnityEngine.Debug.Log(String.Format("{0} bootstrap disabled", VERSION_HANDLER_ASSEMBLY_NAME)); } else { EditorApplication.update -= BootStrap; EditorApplication.update += BootStrap; + // A workaround to make sure bootstrap continues if Unity reloads assemblies + // during bootstrapping. The issue only observed in Unity 2019 and 2020 + float unityVersion = GetUnityVersionMajorMinor(); + if (unityVersion < 2021.0f && unityVersion >= 2019.0f) { + var type = BootStrappedImpl; + } } } @@ -147,7 +153,7 @@ private static void BootStrap() { if (implAvailable) return; var assemblies = new List(); - foreach (string assetGuid in AssetDatabase.FindAssets("l:gvh")) { + foreach (var assetGuid in AssetDatabase.FindAssets("l:gvh")) { string filename = AssetDatabase.GUIDToAssetPath(assetGuid); var match = VERSION_HANDLER_FILENAME_RE.Match(filename); if (match.Success) assemblies.Add(match); @@ -353,12 +359,16 @@ private static string[] StringArrayFromObject(object obj) { /// /// Optional delegate to filter the returned /// list. + /// Directories to search for the assets in the project. Directories + /// that don't exist are ignored. public static string[] SearchAssetDatabase(string assetsFilter = null, - FilenameFilter filter = null) { + FilenameFilter filter = null, + IEnumerable directories = null) { return StringArrayFromObject(InvokeImplMethod("SearchAssetDatabase", null, namedArgs: new Dictionary { { "assetsFilter", assetsFilter }, - { "filter", filter } + { "filter", filter }, + { "directories", directories }, })); } @@ -377,9 +387,7 @@ public static string[] FindAllAssets() { /// public static void UpdateVersionedAssets(bool forceUpdate = false) { InvokeImplMethod("UpdateVersionedAssets", - namedArgs: new Dictionary { - { "forceUpdate", forceUpdate } - }, + args: new object[] { forceUpdate }, schedule: true); } @@ -440,6 +448,7 @@ public static Type FindClass(string assemblyName, string className) { foreach (var currentType in assembly.GetTypes()) { if (currentType.FullName == className) { type = currentType; + break; } } if (type != null) break; @@ -456,7 +465,7 @@ public static Type FindClass(string assemblyName, string className) { /// /// Object to call a method on. /// Name of the method to call. - /// Positional arguments of the method. + /// Positional arguments of the method. /// Named arguments of the method. /// object returned by the method. public static object InvokeInstanceMethod( @@ -472,7 +481,7 @@ public static object InvokeInstanceMethod( /// /// Class to call the method on. /// Name of the method to call. - /// Positional arguments of the method. + /// Positional arguments of the method. /// Named arguments of the method. /// object returned by the method. public static object InvokeStaticMethod( @@ -488,7 +497,7 @@ public static object InvokeStaticMethod( /// Class to call the method on. /// Object to call a method on. /// Name of the method to call. - /// Positional arguments of the method. + /// Positional arguments of the method. /// Named arguments of the method. /// object returned by the method. public static object InvokeMethod( @@ -512,7 +521,10 @@ public static object InvokeMethod( if (position < numberOfPositionalArgs) { var positionalArg = args[position]; // If the parameter type doesn't match, ignore this method. - if (positionalArg != null && parameterType != positionalArg.GetType()) break; + if (positionalArg != null && + !parameterType.IsAssignableFrom(positionalArg.GetType())) { + break; + } parameterValues[position] = positionalArg; matchedPositionalArgs ++; } else if (parameter.RawDefaultValue != DBNull.Value) { @@ -521,7 +533,10 @@ public static object InvokeMethod( object namedArg; if (namedArgs.TryGetValue(parameter.Name, out namedArg)) { // If the parameter type doesn't match, ignore this method. - if (namedArg != null && parameterType != namedArg.GetType()) break; + if (namedArg != null && + !parameterType.IsAssignableFrom(namedArg.GetType())) { + break; + } namedValue = namedArg; matchedNamedArgs ++; } @@ -545,6 +560,87 @@ public static object InvokeMethod( } return foundMethod.Invoke(objectInstance, parameterValues); } + + /// + /// Call a method on a static event. + /// + /// Class to call the method on. + /// The name of the event. + /// Action to add/remove from the event. + /// The func to get the method on the event. + /// True if the method is called successfully. + private static bool InvokeStaticEventMethod(Type type, string eventName, Action action, + Func getMethod) { + EventInfo eventInfo = type.GetEvent(eventName); + if (eventInfo != null) { + MethodInfo method = getMethod(eventInfo); + Delegate d = Delegate.CreateDelegate( + eventInfo.EventHandlerType, action.Target, action.Method); + System.Object[] args = { d }; + if (method.IsStatic) { + method.Invoke(null, args); + return true; + } + } + return false; + } + + /// + /// Call adder method on a static event. + /// + /// Class to call the method on. + /// The name of the event. + /// Action to add from the event. + /// True if the action is added successfully. + public static bool InvokeStaticEventAddMethod(Type type, string eventName, Action action) { + return InvokeStaticEventMethod(type, eventName, action, + eventInfo => eventInfo.GetAddMethod()); + } + + /// + /// Call remover method on a static event. + /// + /// Class to call the method on. + /// The name of the event. + /// Action to remove from the event. + /// True if the action is removed successfully. + public static bool InvokeStaticEventRemoveMethod(Type type, string eventName, Action action) { + return InvokeStaticEventMethod(type, eventName, action, + eventInfo => eventInfo.GetRemoveMethod()); + } + + // Name of UnityEditor.AssemblyReloadEvents.beforeAssemblyReload event. + private const string BeforeAssemblyReloadEventName = "beforeAssemblyReload"; + + /// + /// Register for beforeAssemblyReload event. + /// Note that AssemblyReloadEvents is only availabe from Unity 2017. + /// + /// Action to register for. + /// True if the action is registered successfully. + public static bool RegisterBeforeAssemblyReloadEvent(Action action) { + Type eventType = VersionHandler.FindClass("UnityEditor", + "UnityEditor.AssemblyReloadEvents"); + if (eventType != null) { + return InvokeStaticEventAddMethod(eventType, BeforeAssemblyReloadEventName, action); + } + return false; + } + + /// + /// Unregister for beforeAssemblyReload event. + /// Note that AssemblyReloadEvents is only availabe from Unity 2017. + /// + /// Action to unregister for. + /// True if the action is unregistered successfully. + public static bool UnregisterBeforeAssemblyReloadEvent(Action action) { + Type eventType = VersionHandler.FindClass("UnityEditor", + "UnityEditor.AssemblyReloadEvents"); + if (eventType != null) { + return InvokeStaticEventRemoveMethod(eventType, BeforeAssemblyReloadEventName, action); + } + return false; + } } } // namespace Google diff --git a/source/VersionHandler/test/reflection/Assets/PlayServicesResolver/Editor/TestReflection.cs b/source/VersionHandler/test/reflection/Assets/PlayServicesResolver/Editor/TestReflection.cs index 641f30b0..abaffa31 100644 --- a/source/VersionHandler/test/reflection/Assets/PlayServicesResolver/Editor/TestReflection.cs +++ b/source/VersionHandler/test/reflection/Assets/PlayServicesResolver/Editor/TestReflection.cs @@ -25,6 +25,10 @@ public class Greeter { private string name; + public event Action instanceEvent; + + static public event Action staticEvent; + public Greeter(string name) { this.name = name; } @@ -50,6 +54,16 @@ public static string GenericHelloWithCustomerNameAndPronoun(string customerName, return String.Format("{0} {1}", GenericHelloWithPronoun(pronoun: pronoun), customerName); } + public static string GenericHelloWithCustomerNameAndSuffixes( + string customerName, IEnumerable suffixes = null) { + string fullName = Greeter.GenericHelloWithCustomerName(customerName); + if (suffixes != null) { + foreach (var suffix in suffixes) fullName += " " + suffix; + } + return fullName; + } + + private string MyNameIs() { return String.Format(", my name is {0}", name); } @@ -67,6 +81,18 @@ public string HelloWithCustomerNameAndPronoun(string customerName, return Greeter.GenericHelloWithCustomerNameAndPronoun(customerName, pronoun: pronoun) + MyNameIs(); } + + public static void InvokeStaticEvent() { + if (staticEvent != null) { + staticEvent.Invoke(); + } + } + + public void InvokeInstanceEvent() { + if (instanceEvent != null) { + instanceEvent.Invoke(); + } + } } [UnityEditor.InitializeOnLoad] @@ -91,9 +117,11 @@ static TestReflection() { TestInvokeStaticMethodWithNamedArg, TestInvokeStaticMethodWithArgAndNamedArgDefault, TestInvokeStaticMethodWithArgAndNamedArg, + TestInvokeStaticMethodWithArgAndNamedInterfaceArg, TestInvokeInstanceMethodWithNoArgs, TestInvokeInstanceMethodWithNamedArgDefault, - TestInvokeInstanceMethodWithNamedArg + TestInvokeInstanceMethodWithNamedArg, + TestInvokeStaticEventMethod, }) { var testName = test.Method.Name; Exception exception = null; @@ -213,6 +241,16 @@ static bool TestInvokeStaticMethodWithArgAndNamedArg() { new Dictionary { { "pronoun", "Mrs" } } )); } + // Invoke a static method with a positional and named interface arg. + static bool TestInvokeStaticMethodWithArgAndNamedInterfaceArg() { + IEnumerable suffixes = new string[] { "BSc", "Hons", "PhD", "Kt", "MPerf" }; + return CheckValue("Hello Angie BSc Hons PhD Kt MPerf", + (string)VersionHandler.InvokeStaticMethod( + typeof(Greeter), "GenericHelloWithCustomerNameAndSuffixes", + new object[] { "Angie" }, + new Dictionary { { "suffixes", suffixes } })); + } + // Invoke an instance method with no args. static bool TestInvokeInstanceMethodWithNoArgs() { return CheckValue("Hello, my name is Sam", @@ -236,4 +274,61 @@ static bool TestInvokeInstanceMethodWithNamedArg() { new object[] { "Smith" }, new Dictionary { { "pronoun", "Mrs" } })); } + + // Check if the delegate is properly added/removed from the given event using the function to + // test. + static bool CheckEvent(Func funcToTest, Action invokeEvent, + bool expectSuccess, bool expectInvoked) { + + bool invoked = false; + + Action actionToInvoke = () => { + invoked = true; + }; + + bool result = funcToTest(actionToInvoke); + if (result != expectSuccess){ + throw new Exception( + String.Format("Expect funcToTest returns '{0}', but actually returned '{1}'", + expectSuccess, result)); + } + invokeEvent(); + if ( invoked != expectInvoked) { + throw new Exception(String.Format( + "Expect event invoked: {0}, but actually event invoked: {1}", + expectInvoked, invoked)); + } + + return true; + } + + // Test adding/removing delegate to a static event. + static bool TestInvokeStaticEventMethod() { + CheckEvent( funcToTest: (action) => + VersionHandler.InvokeStaticEventAddMethod(typeof(Greeter), "staticEvent", action), + invokeEvent: Greeter.InvokeStaticEvent, + expectSuccess: true, + expectInvoked: true); + + CheckEvent( funcToTest: (action) => + VersionHandler.InvokeStaticEventRemoveMethod(typeof(Greeter), "staticEvent", + action), + invokeEvent: Greeter.InvokeStaticEvent, + expectSuccess: true, + expectInvoked: false); + + CheckEvent( funcToTest: (action) => + VersionHandler.InvokeStaticEventAddMethod(typeof(Greeter), "foo", action), + invokeEvent: Greeter.InvokeStaticEvent, + expectSuccess: false, + expectInvoked: false); + + CheckEvent( funcToTest: (action) => + VersionHandler.InvokeStaticEventRemoveMethod(typeof(Greeter), "foo", action), + invokeEvent: Greeter.InvokeStaticEvent, + expectSuccess: false, + expectInvoked: false); + + return true; + } } diff --git a/source/VersionHandlerImpl/Properties/AssemblyInfo.cs b/source/VersionHandlerImpl/Properties/AssemblyInfo.cs index 0e474061..ce756cd2 100644 --- a/source/VersionHandlerImpl/Properties/AssemblyInfo.cs +++ b/source/VersionHandlerImpl/Properties/AssemblyInfo.cs @@ -1,4 +1,4 @@ -// +// // Copyright (C) 2014 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,7 +37,7 @@ // if desired. See the Mono documentation for more information about signing. // [assembly: AssemblyDelaySign(false)] -// // +// // // Copyright (C) 2016 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,3 +56,7 @@ // Uses VersionHandlerImpl.RestoreDefaultSettings(preferenceKeys) [assembly: InternalsVisibleTo("Google.IOSResolver")] [assembly: InternalsVisibleTo("Google.JarResolver")] +[assembly: InternalsVisibleTo("Google.PackageManagerResolver")] +[assembly: InternalsVisibleTo("Google.VersionHandlerImplTests")] +[assembly: InternalsVisibleTo("Google.IntegrationTester")] +[assembly: InternalsVisibleTo("Google.PackageManagerClientIntegrationTests")] diff --git a/source/VersionHandlerImpl/VersionHandlerImpl.csproj b/source/VersionHandlerImpl/VersionHandlerImpl.csproj index 0eca6a60..d93cb5a4 100644 --- a/source/VersionHandlerImpl/VersionHandlerImpl.csproj +++ b/source/VersionHandlerImpl/VersionHandlerImpl.csproj @@ -44,6 +44,7 @@ + @@ -52,15 +53,22 @@ + + + + + + + diff --git a/source/VersionHandlerImpl/src/DialogWindow.cs b/source/VersionHandlerImpl/src/DialogWindow.cs new file mode 100644 index 00000000..7d7d37d4 --- /dev/null +++ b/source/VersionHandlerImpl/src/DialogWindow.cs @@ -0,0 +1,431 @@ +// +// Copyright (C) 2020 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Google { + +/// +/// Non-blocking version of dialog implemeted from EditorWindows. +/// The dialog will not block the editor. When multiple dialogs are triggered, they will be queued +/// and only be shown one at a time based on the triggering order. +/// +public class DialogWindow : EditorWindow { + /// + /// Option selected by the dialog. + /// + public enum Option { + + /// + /// Value that can be used as a default to provide different behavior in a non-interactive + /// mode of operation. + /// + SelectedNone = -1, + + /// + /// Option0 was selected by the user. + /// + Selected0 = 0, + + /// + /// Option1 was selected by the user. + /// + Selected1 = 1, + + /// + /// Option2 was selected by the user. + /// + Selected2 = 2, + }; + + // Default width of the dialog. + private const float DEFAULT_WINDOWS_WIDTH = 400.0f; + + /// + /// All the data to render the content of the dialog and react to the user interaction. + /// All the context should be serialable/deserialable so that all the context can be reloaded + /// after Unity hot reloads. + /// + private class DialogContext { + // Title of the dialog. + internal string Title; + + // Message to display in the dialog. + internal string Message; + + // Option selected if interactivity is disabled. + internal Option DefaultOption = Option.SelectedNone; + + // Text for the first option. + internal string Option0String; + + // Text for the second option or null to disable. + internal string Option1String; + + // Text for the third option or null to disable. + internal string Option2String; + + // Width of the dialog window. + internal float WindowWidth = DEFAULT_WINDOWS_WIDTH; + + // Option selected if the dialog is closed. + internal Option WindowCloseOption = Option.SelectedNone; + + // Callback to trigger once a selection is made. + internal Action