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. +* 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. +* 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 +* 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. +* 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. +* 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. +* 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. +* 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. +* 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. +* Automated resolution of out of date pod repositories. # Version 1.2.3 - Oct 25, 2016 ## Bug Fixes - * Fixed exception when reporting conflicting depedencies. +* 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. +* 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. +* 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. +* 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). +* 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. +* 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. +* #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. +* 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. +* Fixes infinite loop of resolution triggered by resolution. 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 c15b8c5f..a9aafe9f 100644 --- a/README.md +++ b/README.md @@ -1,209 +1,903 @@ -# JarResolver-Readme -Google Play Services Jar Resolver Library 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 access to -Google play-services or Android support libraries on Android. The goal is to -minimize the risk of having multiple or conflicting versions of client -libraries included in the Unity project. +## 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: -With this library, a plugin declares the dependencies needed and these are -resolved by using the play-services and support repositories that are part of -the Android SDK. These repositories are used to store .aar files in a Maven -(Gradle) compatible repository. +* Android specific libraries (e.g + [AARs](https://developer.android.com/studio/projects/android-library.html)) -This library implements a subset of the resolution logic used by these build -tools so the same functionality is available in Unity. +* iOS [CocoaPods](https://cocoapods.org/) -# Background -Many Unity plugins have dependencies on Google Play Services. -Dependencies on Google Play Services can cause version conflicts and -duplicate resource definitions. In some cases, including the entire -Google Play Services client library makes it difficult to keep the number -of methods in your app (including framework APIs, library methods, -and your own code) under the 65,536 limit. +* Version management of transitive dependencies -Android Studio addressed this starting with version 6.5 of Play Services. -Starting then, you can include the individual components of Play Services in -your project instead of the entire library. This makes the project overhead -smaller. The result is more resources for your application, -and maybe even a smaller application. +* Management of Package Manager (PM) Registries -The unity-jar-resolver project brings this capability to Unity projects. -Each plugin or application declare the dependency needed e.g. play-services-games, -and the version 8.4+. Then the resolver library copies over the best -version of the play services libraries needed by all the plugins in the project. +If you want to add and use iOS/Android dependencies directly in your project, +then you should to install EDM4U in your project. -To use this plugin, developers need to install the "Support Repository" -and the "Google Repository" in the Android SDK Manager. +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. -Developers can clone this project from GitHub and include it in their project. -Plugin creators are encouraged to adopt this library as well, easing integration for their customers. +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`): -The list of the Play Services components on https://developers.google.com/android/guides/setup. +```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: -# Requirements +```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. -This library only works with Unity version 4.6.8 or higher. +It's possible to change the resolution strategy via the `Assets > External +Dependency Manager > Android Resolver > Settings` menu. -The library relies on the installation of the Android Support Repository and -the Google Repository SDK components. These are found in the "extras" section. +##### Download Artifacts with Gradle -Building using Ubuntu +Using the default resolution strategy, the Android resolver executes the +following operations: -sudo apt-get install monodevelop nunit-console +- Remove the result of previous Android resolutions. E.g Delete all files and + directories labeled with "gpsr" under `Plugins/Android` from the project. -# Packaging +- Collect the set of Android dependencies (libraries) specified by a project's + `*Dependencies.xml` files. -The plugin consists of several C# DLLs that contain - the logic to resolve the dependencies for both Android and iOS (using CocoaPods), -the logic to resolve dependencies and copy them into Unity projects, and logic -to remove older versions of the client libraries as well as older versions of the -JarResolver DLLs. +- Run `download_artifacts.gradle` with Gradle to resolve conflicts and, if + successful, download the set of resolved Android libraries (AARs, JARs). -(Assets/Google Play Services/Resolve Client Jars). In order to support -Unity version 4.x, this class also converts the aar file -to a java plugin project. The second C# file is SampleDependencies.cs -which is the model for plugin developers to copy and add the specific -dependencies needed. +- 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. -During resolution, all the dependencies from all the plugins are merged and resolved. +- Move the processed AARs to `Plugins/Android` so they will be included when + Unity invokes the Android build. -# Usage - 1. Add the unitypackage to your plugin project (assuming you are developing a -plugin). +##### Integrate into mainTemplate.gradle - 2. Copy the SampleDependencies.cs file to another name specific to your plugin -and add the dependencies your plugin needs. +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: -Reflection is used to access the resolver in order to behave correctly when the -project is being loaded into Unity and there is no specific order of class -initialization. +- 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. -For Android dependencies first create and instance of the resolver object: +- 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 + + + + + ``` -// Setup the resolver using reflection as the module may not be - // available at compile time. - Type playServicesSupport = Google.VersionHandler.FindClass( - "Google.JarResolver", "Google.JarResolver.PlayServicesSupport"); - if (playServicesSupport == null) { - return; + +#### 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"); + } + } } - svcSupport = svcSupport ?? Google.VersionHandler.InvokeStaticMethod( - playServicesSupport, "CreateInstance", - new object[] { - "GooglePlayGames", - EditorPrefs.GetString("AndroidSdkRoot"), - "ProjectSettings" - }); +} ``` -Then add dependencies. For example to depend on -play-services-games version 9.6.0, you need to specify the package, artifact, -and version as well as the packageId from the SDK manager in case a updated -version needs to be downloaded from the SDK Manager in order to build. +### 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 + + + ``` - Google.VersionHandler.InvokeInstanceMethod( - svcSupport, "DependOn", - new object[] { - "com.google.android.gms", - "play-services-games", - "9.6.0" }, - namedArgs: new Dictionary() { - {"packageIds", new string[] { "extra-google-m2repository" } } - }); + +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 ``` -The version value supports both specific versions such as 8.1.0, -and also the trailing '+' indicating "or greater" for -the portion of the number preceding the period. For example 8.1.+ would match -8.1.2, but not 8.2. The string "8+" would resolve to any version greater or -equal to 8.0. The meta version 'LATEST' is also supported meaning the greatest -version available, and "0+" indicates any version. -# Android manifest variable processing +### 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 -Some aar files (notably play-services-measurement) contain variables that -are processed by the Android Gradle plugin. Unfortunately, Unity does not perform -the same processing, so this plugin handles known cases of this variable substition -by exploding the aar and replacing ${applicationId} with the bundleID. +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): -# iOS Dependency Management -iOS dependencies are identified using Cocoapods. Cocoapods is run as a post build -process step. The libraries are downloaded and injected into the XCode project -file directly, rather than creating a separate xcworkspace. +```shell +./gradlew build -To add a dependency you first need an instance of the resolver. Reflection is -used to safely handle race conditions when Unity is loading the project and the -order of class initialization is not known. ``` - Type iosResolver = Google.VersionHandler.FindClass( - "Google.IOSResolver", "Google.IOSResolver"); - if (iosResolver == null) { - return; - } + +or Windows: + +```shell +./gradlew.bat build ``` -Dependencies for iOS are added by referring to CocoaPods. The libraries and -frameworks are added to the Unity project, so they will automatically be included. +If Java 11 is not your default Java command, add +`-Dorg.gradle.java.home=` to the command above. + +## Testing -This example add the GooglePlayGames pod, version 5.0 or greater, -disabling bitcode generation. +You can run the tests by running the following from your shell (Linux / OSX): +```shell +./gradlew test ``` - Google.VersionHandler.InvokeStaticMethod( - iosResolver, "AddPod", - new object[] { "GooglePlayGames" }, - namedArgs: new Dictionary() { - { "version", "5.0+" }, - { "bitcodeEnabled", false }, - }); + +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" ``` -# Disabling automatic resolution - -Automatic resolution can be disabled in the Settings dialog, -Assets > Google Play Services > Settings. - -# How it works - -When the dependency is added, the maven-metadata.xml file is read for this -dependency. If there are no versions available (or the dependency is not -found), there is an exception thrown. When the metadata is read, the list of -known versions is filtered based on the version constraint. The remaining list -of version is known as possible versions. - -The greatest value of the possible versions is known as the best version. -The best version is what is used to perform resolution. - -Resolution is done by following the steps: - - 1. All dependencies are added to the "unresolved" list. Then for each dependency -in unresolved: - 2. check if there is already a candidate artifact - 1. if there is not, use the greatest version available (within the constraint) as -the candidate and remove from the unresolved list. - 2. If there is an existing candidate, check if the unresolved version is satisfied -by the candidate version. - 1. If it is, remove it from the unresolved list. - 2. If it is not, remove possible versions from the dependencies that have -non-concrete version constraints (i.e. have a + in the version). - 3. If there - 4. If there are still possible versions to check, add the dependency to the end -of the unresolved list for re-processing with a new version candidate. - 5. If there are no possible versions, then the SDK Manager is used to download -and updated versions of the libraries based on the packageId. - 6. If there still are no possible versions to resolve both the candidate and the -unresolved dependencies, then either fail resolution with an exception, or use -the greatest version value. - 3. When a candidate version is selected, the pom file is read for that version and -the - - 4. If there is a candidate version, add it to the candidate list and remove from -the unresolved. - 3. Process transitive dependencies - 5. for each candidate artifact, read the pom file for dependencies and add them to -the unresolved list. +## 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 76b5c44b..cb824eac 100644 --- a/build.gradle +++ b/build.gradle @@ -2,456 +2,2448 @@ * 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" } /* * Project level variables */ project.ext { - sdk_root = System.getProperty("ANDROID_HOME") - if (sdk_root == null || sdk_root.isEmpty()) { - sdk_root = System.getenv("ANDROID_HOME") - } - - // Determine the current OS. - os_name = System.getProperty("os.name").toLowerCase(); - os_osx = os_name.contains("mac os x"); - os_windows = os_name.contains("windows"); - os_linux = os_name.contains("linux"); - - // Search for the Unity editor executable. - // The Unity editor is required to package the plug-in. - unity_exe = System.getProperty("UNITY_EXE") - if (unity_exe == null || unity_exe.isEmpty()) { - unity_exe = System.getenv("UNITY_EXE") - } - if (unity_exe == null || unity_exe.isEmpty()) { - // TODO: Should probably also search the path using Exec / which / where. - if (os_osx) { - unity_exe ='/Applications/Unity/Unity.app/Contents/MacOS/Unity' - } else if (os_windows) { - unity_exe = 'C:\\Program Files\\Unity\\Editor\\Unity.exe' - } else if (os_linux) { - unity_exe = '/opt/Unity/Editor/Unity' + // Set of properties to cache in the project.properties file. + Properties cacheProperties = new Properties() + + // Set of directories to *not* search under the Unity root directory when + // searching for components of Unity. + String unitySearchDirExcludesString = findProperty("UNITY_EXCLUDES") + String[] unitySearchDirExcludes = + unitySearchDirExcludesString ? + unitySearchDirExcludesString.tokenize(";") : [] + + // Save the current OS. + operatingSystem = OperatingSystem.getOperatingSystem() + + // Default installation path for Unity based upon the host operating system. + List defaultUnityPaths = + [(OperatingSystem.UNKNOWN): ["Unity"], + (OperatingSystem.MAC_OSX): + ["/Applications/Unity/Unity.app/Contents/MacOS/Unity"] + + (new FileNameFinder()).getFileNames( + "/", "Applications/Unity/Hub/Editor/*/Unity.app/Contents/MacOS/Unity"), + (OperatingSystem.WINDOWS): + ["\\Program Files\\Unity\\Editor\\Unity.exe"] + + (new FileNameFinder()).getFileNames( + "\\", "Program Files\\Unity\\Hub\\Editor\\*\\Editor\\Unity.exe"), + (OperatingSystem.LINUX): ["/opt/Unity/Editor/Unity"]][operatingSystem] + + // Search for the Unity editor executable. + // The Unity editor is required to package the plug-in. + for (defaultUnityPath in defaultUnityPaths) { + unityExe = findFileProperty("UNITY_EXE", new File(defaultUnityPath), false) + if (unityExe != null && unityExe.exists()) break; + } + if (unityExe == null || !unityExe.exists()) { + unityExe = findFileInPath(unityExe.name) + } + if (unityExe == null) { + throw new StopActionException("Unity editor executable (UNITY_EXE) not " + + "found") + } + saveProperty("UNITY_EXE", unityExe, cacheProperties) + + // Path fragment that is the parent of the unity executable install location. + // This is used to find the unity root directory from the editor executable. + String unityExeParentPath = + [(OperatingSystem.UNKNOWN): "Editor", + (OperatingSystem.MAC_OSX): "Unity.app/Contents/MacOS", + (OperatingSystem.WINDOWS): "Editor", + (OperatingSystem.LINUX): "Editor"][operatingSystem] + File unityRootDir = findFileProperty( + "UNITY_DIR", new File(unityExe.parentFile.absolutePath - + unityExeParentPath), true) + if (unityRootDir == null) { + throw new StopActionException("Unity root directory (UNITY_DIR) not found.") + } + saveProperty("UNITY_DIR", unityRootDir, cacheProperties) + + FileTree unityRootDirTree = fileTree(dir: unityRootDir) + + // Find unity engine dll under the root directory. + unityDllPath = getFileFromPropertyOrFileTree( + "UNITY_DLL_PATH", true, { + unityRootDirTree.matching { + include "**/Managed/UnityEngine.dll" + exclude unitySearchDirExcludes + } + }) + if (unityDllPath == null) { + throw new StopActionException( + "UnityEngine.dll and UnityEditor.dll directory (UNITY_DLL_PATH) " + + "not found.") + } + saveProperty("UNITY_DLL_PATH", unityDllPath, cacheProperties) + + // iOS runtime dll. This is with the playback engine, so the + // structure is different for MacOS and the others. + unityIosPath = getFileFromPropertyOrFileTree( + "UNITY_IOS_PLAYBACK_PATH", true, { + unityRootDirTree.matching { + include "**/PlaybackEngines/iOSSupport/UnityEditor.iOS.Extensions.dll", + "**/PlaybackEngines/iossupport/UnityEditor.iOS.Extensions.dll" + exclude unitySearchDirExcludes + } + }) + if (unityIosPath == null) { + // iOS support is *required* to build the iOS resolver. + throw new StopActionException( + "UnityEditor.iOS.Extensions.dll directory (UNITY_IOS_PLAYBACK_PATH) " + + "not found.") + } + saveProperty("UNITY_IOS_PLAYBACK_PATH", unityIosPath, cacheProperties) + + // Find xbuild to build the dlls. + xbuildExe = getFileFromPropertyOrFileTree( + "XBUILD_EXE", false, { + unityRootDirTree.matching { + include (operatingSystem == OperatingSystem.WINDOWS ? + "**/bin/xbuild.bat" : "**/xbuild") + exclude unitySearchDirExcludes + } + }) + if (xbuildExe == null) { + throw new StopActionException("xbuild not found (XBUILD_EXE)") + } + saveProperty("XBUILD_EXE", xbuildExe, cacheProperties) + + // Find mono to determine the distribution being used. + monoExe = getFileFromPropertyOrFileTree( + "MONO_EXE", false, { + unityRootDirTree.matching { + include (operatingSystem == OperatingSystem.WINDOWS ? + "**/bin/mono.bat" : "**/bin/mono") + exclude unitySearchDirExcludes + } + }) + saveProperty("MONO_EXE", monoExe, cacheProperties) + + // 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) } + return null } - unity_exe_found = (new File(unity_exe)).exists(); - if (!unity_exe_found) { - logger.warn('Unity editor executable not found, plug-in packaging ' + - 'may fail.') - } - - // find the unity root directory by working up from the executable. - // not, perfect, but pretty close, work up the tree until the - // name starts with unity. - unity_root = (new File(unity_exe)).getParentFile().getParentFile(); - while (!unity_root.name.toLowerCase().startsWith("unity")) { - if (unity_root.getParentFile() != null) { - unity_root = unity_root.getParentFile(); - } else { - break; - } + 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)) + } + + // 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(scriptDirectory,"gradle.properties") + if (!projectPropertiesFile.exists()) { + logger.info(sprintf("Saving %s to %s", + cacheProperties.stringPropertyNames(), + projectPropertiesFile)) + cacheProperties.store(projectPropertiesFile.newWriter(), null) + } + + // UnityAssetUploader required environment variables. + unityUsername = findProperty("UNITY_USERNAME") + unityPassword = findProperty("UNITY_PASSWORD") + unityPackageId = findProperty("UNITY_PACKAGE_ID") + unityPackagePath = findFileProperty("UNITY_PACKAGE_PATH", null) + + // 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. + pluginTemplateDir = new File(scriptDirectory, "plugin") + // Directory where the plugin is staged to be exported as a Unity package. + 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, 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", "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(scriptDirectory, "source") + // Solution which references all projects used by the plugin. + 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" + // 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" +} + +/* + * Host operating system. + */ +public enum OperatingSystem { + UNKNOWN, MAC_OSX, WINDOWS, LINUX + + /* + * Get the current operating system. + * + * @returns Current host operating system. + */ + public static OperatingSystem getOperatingSystem() { + String os_name = System.getProperty("os.name").toLowerCase() + if (os_name.contains("mac os x")) { + return OperatingSystem.MAC_OSX + } else if (os_name.contains("windows")) { + return OperatingSystem.WINDOWS + } else if (os_name.contains("linux")) { + return OperatingSystem.LINUX } + return OperatingSystem.UNKNOWN + } +} - logger.info( "Unity root is $unity_root") +/* + * 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 - // find unity engine dll - unity_dll_path = findUnityPath("UNITY_DLL_PATH", true, - fileTree(dir: unity_root).matching { - include '**/Managed/UnityEngine.dll'}); + // A complete set of all enums + private static HashSet completeSet; - if (unity_dll_path == null || !unity_dll_path.exists()) { - logger.warn('Unity Editor and Runtime DLLs not found, compilation may fail!') - } else { - logger.info("Unity Engine DLL path is $unity_dll_path") - } - - // ios runtime dll. This is with the playback engine, so the - // structure is different for MacOS and the others. - unity_ios_dll_path = findUnityPath("UNITY_IOS_PLAYBACK_PATH", true, - os_osx ? - fileTree(dir: unity_root.parentFile).matching { - include '**/PlaybackEngines/iOSSupport/UnityEditor.iOS.Extensions.dll' - } : - fileTree(dir: unity_root).matching { - include '**/PlaybackEngines/iOSSupport/UnityEditor.iOS.Extensions.dll' - }) - - if (unity_ios_dll_path == null || !unity_ios_dll_path.exists()) { - logger.warn('Unity iOS Playback engine not found, compilation may fail!') - } else { - logger.info("Unity iOS Playback engine is $unity_ios_dll_path") + /* + * 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 + } +} - // find the NUnit framework dll. - unity_nunit_dll_path = findUnityPath("UNITY_NUNIT_PATH", true, - fileTree(dir: unity_root).matching { - include '**/nunit.framework.dll' - }) +/* + * 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 - if (unity_nunit_dll_path == null || !unity_nunit_dll_path.exists()) { - logger.warn('Unity NUnit framework not found, compilation may fail!') - } else { - logger.info("Unity NUnity framework found in $unity_nunit_dll_path") + // 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() + } -// xbuild is used to build the dlls. - if (os_windows) { - xbuild_exe = findUnityPath("XBUILD_EXE", false, - fileTree(dir: unity_root).matching { include '**/Mono/bin/xbuild.bat'}) - } else if (os_osx || os_linux) { - xbuild_exe = findUnityPath("XBUILD_EXE", false, - fileTree(dir: unity_root).matching { include '**/xbuild'}) + /* + * 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; } - if (xbuild_exe == null || !xbuild_exe.exists()) { - logger.warn("xbuild command not found, compilation may fail.") - xbuild_exe = null - } else { - logger.info("xbuild found at $xbuild_exe") - } - -// nunit-console is used to run tests. - if (os_windows) { - nunit_console_exe = findUnityPath("NUNIT_CONSOLE_EXE", false, - fileTree(dir: unity_root).matching { include '**/Mono/bin/nunut-console2.bat'}) - } else if (os_osx) { - nunit_console_exe = findUnityPath("NUNIT_CONSOLE_EXE", false, - fileTree(dir: unity_root).matching { include '**/nunit-console2'}) - } else if (os_linux) { - nunit_console_exe = findUnityPath("NUNIT_CONSOLE_EXE", false, - fileTree(dir: unity_root).matching { include '**/nunit-console'}) - } - if (nunit_console_exe == null || !nunit_console_exe.exists()) { - logger.warn("nunit_console command not found, compilation may fail.") - nunit_console_exe = null - } else { - logger.info("nunit_console found at $nunit_console_exe") + 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 + } +} - pluginSrc = file('plugin').absolutePath - pluginProj = file('build/PluginSrc').absolutePath - buildPath = file('build').absolutePath - exportPath = file('build/plugin.unitypackage').absolutePath - dllDir = 'Assets/PlayServicesResolver/Editor' - pluginVersion = '1.2.11.0' - currentPluginPath = file('.').absolutePath - currentPluginBasename = 'play-services-resolver' - currentPluginName = (currentPluginBasename + '-' + pluginVersion + - '.unitypackage') +/* + * 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) } -task compile_resolverTests(type: Exec) { - description 'Compile the tests for the Mono framework component.' - workingDir 'source' - commandLine "${xbuild_exe}", '/target:JarResolverTests', - "/property:NUnityHintPath=$unity_nunit_dll_path.absolutePath" - ext.remoteTaskPhase = 'build' +/* + * 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 } -task test_resolverLib(type: Exec, dependsOn: compile_resolverTests) { - description 'Runs the tests.' - workingDir 'source' - commandLine "$nunit_console_exe", - "JarResolverTests/bin/Debug/JarResolverTests.dll" - ext.remoteTaskPhase = 'build' +/* + * 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) + } } -task compile_packageManagerTests(type: Exec) { - description 'Compile the tests for the Mono framework component.' - workingDir 'source' - commandLine "${xbuild_exe}", '/target:PackageManagerTests', - "/property:NUnityHintPath=$unity_nunit_dll_path.absolutePath" - ext.remoteTaskPhase = 'build' +/* + * Test session + */ +public class TestSession { + public String name; + public TestTypeEnum type; + public TestModuleEnum module; + public Boolean isPassed; } -task test_packageManager(type: Exec, dependsOn: compile_packageManagerTests) { - description 'Runs the tests.' - workingDir 'source' - commandLine "$nunit_console_exe", - "PackageManagerTests/bin/Debug/PackageManagerTests.dll" - ext.remoteTaskPhase = 'build' +/* + * Search the path variable for an executable file. + * + * @param filename Name of the file to search for. + * + * @return If found, the File object that references the file, null otherwise. + */ +File findFileInPath(String filename) { + def stdout = new ByteArrayOutputStream() + exec { + executable OperatingSystem.getOperatingSystem() == OperatingSystem.WINDOWS ? + "where" : "which" + args filename + ignoreExitValue true + standardOutput = stdout + } + String resultString = stdout.toString() + return resultString.isEmpty() ? null : new File(resultString) } -/// find paths within the Unity file tree used for building. -def findUnityPath(propertyKey, wantDirectory, fileTree) { +/* + * Get a property value by name searching project properties, system properties + * and environment variables. + * + * @param propertyName Name of the property to search for. + * @param defaultValue Value of the property if it's not found. + * + * @returns Property value as string if found and not empty, null otherwise. + */ +String findProperty(String propertyName, String defaultValue = null) { + Closure valueIsSet = { + valueString -> valueString != null && !valueString.isEmpty() + } + String value = null + for (def queryObject in [project, System]) { + if (queryObject.hasProperty(propertyName)) { + value = queryObject.getProperty(propertyName) + if (valueIsSet(value)) { + return value + } + } + } + value = System.getenv(propertyName) + return valueIsSet(value) ? value : defaultValue +} - def propValue; - def fileValue; +/* + * Get a property value by name as a file, searching project properties, + * system properties and environment variables. + * + * @param propertyName Name of the property to search for. + * @param defaultValue Value of the property if it's not found. + * @param mustExist Whether the file must exist. + * + * @returns Property value as a File if found and exists (if mustExist is true), + * null otherwise. + */ +File findFileProperty(String propertyName, File defaultValue = null, + Boolean mustExist = false) { + String foundFilePath = findProperty( + propertyName, defaultValue != null ? defaultValue.absolutePath : null) + File foundFile = foundFilePath != null ? new File(foundFilePath) : null + return foundFile != null && (!mustExist || foundFile.exists()) ? + foundFile : null +} - propValue = System.getProperty(propertyKey) - if (propValue == null || propValue.isEmpty()) { - propValue = System.getenv(propertyKey) +/* + * Get a File from the specified property or the shortest path in the specified + * FileTree object. + * + * @param propertyName Property name to lookup prior to searching the tree + * for a matching file. + * @param useParentDirectory If set to true, this returns a File object pointing + * at the parent directory of the file that is found. + * @param fileTreeClosure Closure which returns a FileTree object to search. + * + * @return File if it's found and exists, null otherwise. + */ +File getFileFromPropertyOrFileTree(String propertyName, + Boolean useParentDirectory, + fileTreeClosure) { + File fileValue = findFileProperty(propertyName, null, true) + if (fileValue == null) { + // Search for the shortest path to the require file. + fileTreeClosure().files.each { currentFile -> + if (fileValue == null || + fileValue.absolutePath.length() > currentFile.absolutePath.length()) { + fileValue = currentFile + } } - // convert string to file object - if (propValue != null) { - fileValue = file(propValue) + if (useParentDirectory && fileValue != null) { + fileValue = fileValue.parentFile + } + } + return fileValue +} + +/* + * Set a project property and log it. + * + * @param name Name of the property. + * @param value Value of the property. + * @param properties Map of properties to save to. + */ +void saveProperty(String name, value, Properties properties) { + if (value != null) properties.setProperty(name.toString(), value.toString()) + logger.info(sprintf("%s: %s", name, value)) +} + +/* + * Removes the extension from a filename. + * + * @param fileObj File object to split into a basename and extension. + * + * @returns (basename, extension) tuple where basename is the filename without + * an extension and extension is the extension. + */ +List splitFilenameExtension(File fileObj) { + String filename = fileObj.name + 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) + return [basename, extension] +} + +/* + * Construct the name of a versioned asset from the source filename and version + * 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, 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}_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())) { + if (useVersionDir) { + dllDir = new File(dllDir, version) } else { - fileValue = null + targetName += (fullVersionPrefix ? "_version-" : "_v") + version } + } + String filename = targetName + postfix + extension + return new File(dllDir, filename) +} - if (fileValue == null || !fileValue.exists()) { - // take the shortest path location. - fileValue = null - fileTree.files.each { p -> - if (fileValue == null || - fileValue.absolutePath.length() > p.absolutePath.length()) { - fileValue = p; - } - } +/* + * 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 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 - if (wantDirectory) { - return fileValue != null ? fileValue.parentFile : null; - } else { - return fileValue + // 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) +} + +/* + * Copy a file. + * + * @param sourceFile File to copy. + * @param targetFile File to write to. + * + * @returns targetFile. + */ +File copyFile(File sourceFile, File targetFile) { + targetFile.parentFile.mkdirs() + logger.info(sprintf("Copy %s -> %s", sourceFile.path, targetFile.path)) + targetFile.newOutputStream() << sourceFile.newInputStream() + return targetFile +} + +/* + * Copy a list of files from one directory into another. + * + * @param taskName Name of the task. + * @param taskDescription Description of the task. + * @param filesToCopy List of files to copy with paths relative to the source + * directory. + * @param sourceDir Directory to copy files from. + * @param targetDir Directory to copy files into preserving the relative path of + * each file. + * @param dependsOn List of dependencies for the task. + * @param copyFileClosure Closure which takes (sourceFile, targetFile) to copy a + * file. + * + * @returns Task which copies the specified files. The ext.sourceTargetFileMap + * property of the task contains the mapping of source to target files to be + * copied by the task. + */ +Task createCopyFilesTask(String taskName, String taskDescription, + Iterable filesToCopy, File sourceDir, + File targetDir, Iterable dependsOn, + Closure copyFileClosure) { + Map sourceTargetFileMap = filesToCopy.collectEntries { + [(new File(sourceDir, it.path)), (new File(targetDir, it.path))] + } + if (!copyFileClosure) { + copyFileClosure = { + sourceFile, targetFile -> copyFile(sourceFile, targetFile) } + } + Task copyTask = tasks.create(name: taskName, + description: taskDescription, + type: Task, + dependsOn: dependsOn).with { + inputs.files sourceTargetFileMap.keySet() + outputs.files sourceTargetFileMap.values() + doLast { + sourceTargetFileMap.each { sourceFile, targetFile -> + copyFileClosure(sourceFile, targetFile) + } + } + } + copyTask.ext.sourceTargetFileMap = sourceTargetFileMap + return copyTask } -// Construct the name of a versioned asset from the source filename and version -// string. -// The encoded string takes the form... -// ${filename}_v${version}_.${extension} -// where extension is derived from the specified filename. -def versionedAssetName(filename, version) { - def extensionIndex = filename.lastIndexOf('.') - def basename = filename.substring(0, extensionIndex) - def extension = filename.substring(extensionIndex) - // Encode the DLL version and target names into the DLL in the form... - // ${dllname}_t${hypen_separated_target_names}_v${version}.dll - def targetName = basename - if (version != null && !version.isEmpty()) { - targetName += '_v' + version - } - return targetName + extension -} - -// Generate the tasks to compile a plugin DLL and copy it to the Unity -// plugin packaging folder. -def build_pluginDll(projectName, assemblyDllBasename) { - def version = "${pluginVersion}" - def projectPath = 'source/' + projectName + '/' + projectName + '.csproj' - def assemblyDll = versionedAssetName(assemblyDllBasename, version) - - def compileTask = tasks.create(name: "compile_" + projectName, - type: Exec, - description: 'Compile ' + projectName - ) - compileTask.workingDir('source') - compileTask.commandLine(["${xbuild_exe}", "/target:" + projectName, - "/property:UnityHintPath=$unity_dll_path.absolutePath", - "/property:UnityIosPath=$unity_ios_dll_path.absolutePath" - ]) - - compileTask.ext.remoteTaskPhase = "build" - def assemblyDllSource = file("source/" + projectName + "/bin/Debug/" + - assemblyDllBasename) - - def dllMetaBasename = assemblyDllBasename + ".meta" - def dllMeta = "${pluginSrc}/${dllDir}/" + dllMetaBasename - def dllMetaVersioned = assemblyDll + ".meta" - def copyTask = tasks.create(name: "copy_" + projectName, - type: Copy, - description: ('Copy ' + projectName + - ' to unity packaging folder'), - dependsOn: compileTask) - copyTask.from(files(assemblyDllSource, dllMeta)) - copyTask.into("${pluginProj}/${dllDir}") - copyTask.duplicatesStrategy('include') - copyTask.rename({ - String fn -> - if (fn.endsWith(dllMetaBasename)) { - return fn.replace(dllMetaBasename, dllMetaVersioned) - } else if (fn.endsWith(assemblyDllBasename)) { - return fn.replace(assemblyDllBasename, assemblyDll) - } - return fn - }) - copyTask.ext.remoteTaskPhase = "build" -} - -build_pluginDll("PlayServicesResolver", "Google.JarResolver.dll") -build_pluginDll("VersionHandler", "Google.VersionHandler.dll") -build_pluginDll("IOSResolver", "Google.IOSResolver.dll") -build_pluginDll("PackageManager", "Google.PackageManager.dll") - -task update_metadataForVersion() { - description 'Update plugin metadata if the plugin version changed.' - if (!file(currentPluginName).exists()) { - for (fileobj in fileTree("${pluginSrc}")) { - def lines = fileobj.text.split('\n') - def outputLines = [] - for (line in lines) { - if (line.contains('guid:')) { - line = ( - 'guid: ' + java.util.UUID.randomUUID().toString().replace('-', '')) +/* + * Copy a file, injecting release information if it's a Unity asset metadata + * file. + * + * @param sourceFile File to copy from. + * @param targetFile File to copy to. + * + * @return targetFile. + */ +File copyAssetMetadataFile(File sourceFile, File targetFile) { + if (!sourceFile.name.endsWith(project.ext.unityMetadataExtension)) { + return copyFile(sourceFile, targetFile) + } + 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 -> + def versionMatch = line =~ versionRegEx + def folderMatch = line =~ folderAssetRegEx + if (versionMatch.matches()) { + currentVersion = versionMatch.group(2) + } else if (folderMatch.matches()) { + isFolder = true + } + } + Boolean isNotVersioned = (isFolder || + targetFile.name.startsWith( + "play-services-resolver")) + // Ignore folder assets, they don't need to be versioned. + if (isNotVersioned) return copyFile(sourceFile, targetFile) + Boolean versionChanged = currentVersion != project.ext.pluginVersion + + List outputLines = [] + lines.each { line -> + if (versionChanged) { + def guidMatch = (line =~ /^(guid:)\s+(.*)/) + def versionLabelMatch = (line =~ versionRegEx) + 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. + line = sprintf( + "%s %s", + guidMatch.group(1), + java.util.UUID.randomUUID().toString().replace("-", "")) + } 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) + // If the metadata file does not contain a version label, inject it. + if (currentVersion.isEmpty() && line.startsWith("labels:")) { + outputLines.add(sprintf("- gvh_version-%s", project.ext.pluginVersion)) + } + } + targetFile.write(outputLines.join("\n") + "\n") + + return targetFile +} + +/* + * Build a project with xbuild. + * + * @param taskName Name of the task. + * @param taskDescription Description of the task. + * @param projectToBuild Path to the project to build. + * @param target Target to build in the project. + * @param inputFiles Input files for the project. + * @param outputDir Output directory. + * @param outputFiles List of output file paths relative to the output + * directory. + * @param dependsOn List of dependencies for the task. + * + * @returns Task which builds the specified target. + */ +Task createXbuildTask(String taskName, String taskDescription, + File projectToBuild, String target, + Iterable inputFiles, File outputDir, + Iterable outputFiles, Iterable dependsOn) { + File intermediatesDir = new File(outputDir, "obj") + File binaryOutputDir = new File(outputDir, "bin") + 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 + Task patchVersionTask = tasks.create( + name: taskName + "AddVersionTo" + it.name, + description: "Add version to " + it.path, + type: Task, + dependsOn: dependsOnTasks) + patchVersionTask.with { + inputs.files files([versionFile]) + outputs.files files([versionFile]) + doLast { + String[] lines = versionFile.text.split("\n") + List outputLines = lines.collect { + it.replaceAll( + /(^.*VERSION_STRING[ ]*=[ ]*\")([^\"]+)(\".*)/, + '$1' + project.ext.pluginVersion + '$3') + } + String patchedFileString = (outputLines.join("\n") + "\n") + if (versionFile.text != patchedFileString) { + print("Patch " + versionFile.name) + versionFile.write(patchedFileString) + } } - outputLines.add(line) } - fileobj.write(outputLines.join('\n') + '\n') + return patchVersionTask } } + if (patchVersionFilesTasks) { + compileTaskDependencies += patchVersionFilesTasks + } + + Task task = tasks.create(name: taskName, + description: taskDescription, + type: Task, + dependsOn: compileTaskDependencies).with { + inputs.files inputFiles + outputs.files files(outputFilesInBinaryOutputDir) + 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 } -task copy_pluginTemplate(type: Copy, dependsOn: update_metadataForVersion) { - description 'Copy the template project into the Unity plugin packaging dir.' - from "${pluginSrc}" - into "${pluginProj}" - exclude '**/*.dll.*', '**/*.txt.meta' - duplicatesStrategy 'include' - ext.remoteTaskPhase = 'build' +/* + * Generates tasks to compile a plugin DLL and copy it to plugin staging + * area directory. + * + * @param componentName Basename of the generated tasks. + * @param projectName Name of the project under + * source/${projectName}/${projectName}.csproj to build. + * @param assemblyDllBasename Basename of the output DLL generated from the + * specified project. + * @param versionDll Whether the output DLL filename should contain the plugin + * version. + * @param dependsOn List of tasks that should be completed before this plugin + * task is executed. + * + * @returns Task that builds the DLL to the build output folder. + */ +Task createBuildPluginDllTask(String componentName, + String projectName, + String assemblyDllBasename, + Boolean versionDll, + Iterable dependsOn = null) { + File projectDir = new File(project.ext.pluginSourceDir, projectName) + File projectBuildDir = new File(project.ext.buildDir, projectName) + + // 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, + [new File(assemblyDllBasename)], dependsOn) + compileTask.ext.componentName = componentName + + // Template metadata for the built DLL. + 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) + + File stagingDir = new File(new File(projectBuildDir, + project.ext.pluginStagingAreaDir.name), + project.ext.pluginEditorDllDir.path) + Map stagingFileMap = + unversionedFiles.collectEntries { File unversionedFile -> + File unversionedOutputFile = new File(stagingDir.path, + unversionedFile.name) + return [ + unversionedFile.path, + versionDll ? + versionedAssetFile(unversionedOutputFile, false, "", true) : + unversionedOutputFile] + } + + // Copy files to the staging directory for this project. + Task copyTask = tasks.create( + name: sprintf("copy%sStaging", componentName), + type: Task, + description: sprintf("Copy %s DLLs to the plugin staging dir.", + projectName), + dependsOn: [compileTask]).with { + inputs.files unversionedFiles + outputs.files stagingFileMap.values() + doLast { + inputs.files.each { + File inputFile -> + copyAssetMetadataFile(inputFile, stagingFileMap[inputFile.path]) + } + } + } + copyTask.ext.stagingDir = stagingDir + copyTask.ext.componentName = componentName + + // Generate a clean target for this project. + Task cleanTask = tasks.create(name: sprintf("clean%s", componentName), + description: sprintf("Clean %s plugin DLL", projectName), + type: Delete).with { + delete (files(compileTask.outputs.files, + copyTask.outputs.files, + projectBuildDir)) + } + cleanTask.ext.componentName = componentName + + // Return the build target for this project. + Task buildTask = + tasks.create(name: sprintf("build%s", componentName), + description: sprintf("Build %s plugin DLL", + projectName), + dependsOn: [copyTask]) + buildTask.ext.componentName = componentName + return buildTask } -task inject_versionIntoMetaFiles(dependsOn: [copy_pluginTemplate, - 'copy_PlayServicesResolver', - 'copy_VersionHandler', - 'copy_IOSResolver', - 'copy_PackageManager']) { - description 'Inject the version number into the plugin\'s meta files.' - ext.remoteTaskPhase = 'build' - doLast { - for (fileobj in fileTree("${pluginProj}")) { - if (fileobj.path.endsWith('.meta')) { - def lines = fileobj.text.split('\n') - def outputLines = [] - for (line in lines) { - outputLines.add(line) - if (line.contains('labels:')) { - outputLines.add("- gvh_v${pluginVersion}") - } +/* + * 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 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 that does nothing. + */ +Task createEmptyTask(String taskName, String summary, + Iterable dependsOn) { + Task emptyTask = tasks.create(name: taskName, + description: sprintf("(disabled) %s", summary), + dependsOn: dependsOn) + emptyTask.with { + doLast { + logger.info(sprintf("%s disabled", taskName)) + } + } + return emptyTask +} + +/* + * Create a task to execute Unity. + * + * @param taskName Name of the task to create. + * @param summary Description of the operation being performed with Unity. + * "create" is a reserved operation that creates a project. This is used to + * generate the log file name so should be unique for the project and ideally + * not contain whitespace. + * @param dependsOn Dependencies of the new task. + * @param projectName Name of the project directory to use. + * @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: + * - ext.projectDir: Directory of the created Unity project. + * - ext.containerDir: Directory which contains the Unity project and the logs. + * This is the same as projectContainerDir. + * - ext.logFile: Unity log file from the operation. + */ +Task createUnityTask(String taskName, String summary, + Iterable dependsOn, String projectName, + File projectContainerDir, Iterable arguments, + 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 += project.ext.unityBatchModeArguments + } else { + executeArguments += project.ext.unityInteractiveModeArguments + } + executeArguments += [ + "-logFile", logFile.absolutePath, + createProject ? "-createProject" : "-projectPath", projectDir.absolutePath] + if (createProject) executeArguments += ["-quit"] + executeArguments += arguments + + 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) + } + unityTask.ext.projectDir = projectDir + unityTask.ext.containerDir = projectContainerDir + unityTask.ext.logFile = logFile + return unityTask +} + +/* + * Create a task that generates a Unity project. + * + * @param taskName Name of the task. + * @param dependsOn Dependencies of the new task. + * @param projectName Name of the project directory to create. + * @param projectContainerDir Directory to create the project directory in. + * + * @returns Task which executes Unity. + * 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. + * This is the same as projectContainerDir. + * - ext.logFile: Unity log file from the operation. + */ +Task createUnityProjectTask(String taskName, Iterable dependsOn, + String projectName, File projectContainerDir) { + return createUnityTask(taskName, "create", dependsOn, projectName, + projectContainerDir, [], true, null).with { + doFirst { + // Clean / create the output directory. + delete ext.containerDir + ext.containerDir.mkdirs() + } + } +} + +/* + * Creates a task which runs a test with the Unity plugin. + * + * @param taskName Name of the test. + * @param description Description of the task. + * @param dependsOn Dependencies of the new task. + * @param testScriptsDir Directory containing scripts to copy into the test + * project and execute for testing. + * @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, 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. + 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 } - fileobj.write(outputLines.join('\n') + '\n') } + doLast { + delete updaterScriptDir + } + } + setupTasks += [versionHandlerUpdate] + } + + List copyTasks = [] + + // Task which copies test scripts into the project. + 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", + copyTasks, + setupTestProject.ext.projectDir.name, + setupTestProject.ext.containerDir, + 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), + description: sprintf("Clean %s", taskName), + type: Delete).with { + delete setupTestProject.ext.containerDir + } + + return testTask +} + +/* + * Creates a task which runs tests with the Unity plugin in batch and + * non-batch modes. + * + * @param taskName Name of the test. + * @param description Description of the task. + * @param dependsOn Dependencies of the new task. + * @param testScriptsDir Directory containing scripts to copy into the test + * project and execute for testing. + * @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 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, editorAssets, + additionalArguments, true, createTaskClosure, + testType, testModule, + enableDlls, upm_package_manifest), + createUnityTestTask( + sprintf("%sInteractiveMode", taskName), + sprintf("%s (Interactive Mode)", description), + dependsOn, testScriptsDir, editorAssets, + additionalArguments, false, createTaskClosure, + testType, testModule, + enableDlls, upm_package_manifest)]) } -task copy_manifestMetadata(type: Copy) { - def manifestBasename = versionedAssetName( - currentPluginBasename + '.txt', null) + '.meta' - description 'Copy .meta file for the plugin manifest.' - from file("${pluginSrc}/${dllDir}/" + manifestBasename) - into file("${pluginProj}/${dllDir}/") - rename { - String fn -> - return fn.replace(manifestBasename, - versionedAssetName(currentPluginBasename + '.txt', - "${pluginVersion}") + - '.meta') +/* + * 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) + } +} + +/* + * 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 +} + +/* + * 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 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 +} + + +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" +} + + +/* + * 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) +} + +/* + * 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) + } } - ext.remoteTaskPhase = 'prebuild' } -task generate_manifest(dependsOn: ['copy_manifestMetadata', - 'inject_versionIntoMetaFiles']) { - description 'Generate a manifest for the files in the plug-in.' +Task reportAllTestsResult = tasks.create ( + name: "reportAllTestsResult", + description: "Report the result all every test that has been run", + type: Task +).with { doLast { - def dir = file("${pluginProj}/Assets") - def list = [] - dir.eachFileRecurse(groovy.io.FileType.FILES) { filename -> - def path = filename.path - if (!(path.toLowerCase().endsWith('.meta') || - path.toLowerCase().endsWith('.txt'))) { - list << filename.path.replace("${pluginProj}/", '') + 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) } - def manifest = file("${pluginProj}/${dllDir}/" + - versionedAssetName(currentPluginBasename + '.txt', - "${pluginVersion}")) - manifest.write(list.join('\n') + '\n') } - ext.remoteTaskPhase = 'build' + +task updateEmbeddedGradleWrapper(type: Zip) { + description "Update the gradle wrapper in gradle-template.zip" + from project.ext.scriptDirectory + include "gradlew" + include "gradlew.bat" + include "gradle/**" + archiveName "gradle-template.zip" + destinationDir (new File(project.ext.scriptDirectory, + "source/AndroidResolver/scripts")) } -task tests(dependsOn: ['test_resolverLib','test_packageManager']) +Task buildVersionHandler = createBuildPluginDllTask( + "VersionHandler", "VersionHandler", "Google.VersionHandler.dll", false) +Task buildVersionHandlerImpl = createBuildPluginDllTask( + "VersionHandlerImpl", "VersionHandlerImpl", "Google.VersionHandlerImpl.dll", + true, [buildVersionHandler]) +Task buildAndroidResolver = createBuildPluginDllTask( + "AndroidResolver", "AndroidResolver", "Google.JarResolver.dll", true, + [updateEmbeddedGradleWrapper, buildVersionHandlerImpl]) +Task buildIosResolver = createBuildPluginDllTask( + "IosResolver", "IOSResolver", "Google.IOSResolver.dll", true, + [buildAndroidResolver]) +Task buildPackageManagerResolver = createBuildPluginDllTask( + "PackageManagerResolver", "PackageManagerResolver", + "Google.PackageManagerResolver.dll", true, + [buildVersionHandlerImpl]) -task export_package(dependsOn: 'generate_manifest') { - description 'Creates and exports the Plugin unity package.' +task preparePluginStagingAreaDir(type: Task) { + description "Delete all files that should not be present in staging area." doLast { - def argv = ["-g.building", - "-buildTarget", - "android", - "-batchmode", - "-projectPath", - "${pluginProj}", - "-logFile", - "build/unity.log", - "-exportPackage", - "Assets/PlayServicesResolver", - "${exportPath}", - "-quit"] - exec { - executable "${unity_exe}" - args argv + Set excludePaths = ( + files( + copyPluginTemplateToStagingArea.outputs.files, + copyPluginComponentsToStagingArea.outputs.files, + generatePluginManifest.outputs.files).files.collect { + it.absolutePath + }).toSet() + fileTree(project.ext.pluginStagingAreaDir).each { fileObj -> + if (!excludePaths.contains(fileObj.absolutePath)) { + delete fileObj + } } } - ext.remoteTaskPhase = 'build' +} - dependsOn tests +Task copyPluginTemplateToStagingArea = createCopyFilesTask( + "copyPluginTemplateToStagingArea", + "Copy the template project into the Unity plugin packaging dir.", + [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, + copyPackageManagerResolverStaging].collect { + task -> + createCopyFilesTask( + sprintf("copy%sToStagingArea", task.ext.componentName), + sprintf("Copy %s into the Unity plugin packaging dir.", + task.ext.componentName), + task.outputs.files.collect { + new File(it.absolutePath - task.ext.stagingDir.absolutePath) + }, + task.ext.stagingDir, + new File(project.ext.pluginStagingAreaDir, + project.ext.pluginEditorDllDir.path), + [task], null) } -task copy_plugin() { - description 'Copy plugin to the current-build directory' - doFirst { - // If the version number has been bumped delete the exploded directory. - if (!file(currentPluginName).exists()) { - delete file(currentPluginPath +"/exploded") +task copyPluginComponentsToStagingArea( + dependsOn: copyComponentsToStagingAreaTasks) { + description "Copy plugin components into the Unity plugin packaging dir." + outputs.files files(copyComponentsToStagingAreaTasks.collect { + task -> task.outputs.files + }) +} + +task generatePluginManifest(dependsOn: [preparePluginStagingAreaDir, + copyPluginTemplateToStagingArea, + copyDocumentationToStagingArea, + copyPluginComponentsToStagingArea]) { + String unversionedManifestName = currentPluginBasename + ".txt" + File outputDir = new File(project.ext.pluginStagingAreaDir, + project.ext.pluginEditorDllDir.path) + File manifestMetadataTemplateFile = + new File(new File(project.ext.pluginTemplateDir, + project.ext.pluginEditorDllDir.path), + unversionedManifestName + project.ext.unityMetadataExtension) + File manifestFile = versionedAssetFile( + new File(outputDir, unversionedManifestName), + true, + "_manifest", + false) + File manifestMetadataFile = versionedAssetFile( + new File(outputDir, manifestMetadataTemplateFile.name), + true, + "_manifest", + false) + + description "Generate a manifest for the files in the plug-in." + inputs.files files(manifestMetadataTemplateFile) + outputs.files files(manifestFile, manifestMetadataFile) + + doLast { + List filenameList = [] + (new File(project.ext.pluginStagingAreaDir, "Assets")).eachFileRecurse( + groovy.io.FileType.FILES) { File fileObj -> + String absolutePath = fileObj.absolutePath + String absolutePathLowerCase = absolutePath.toLowerCase() + if (!(absolutePathLowerCase.endsWith( + project.ext.unityMetadataExtension) || + absolutePathLowerCase.endsWith(".txt"))) { + filenameList.add( + absolutePath.replace( + project.ext.pluginStagingAreaDir.absolutePath + File.separator, "")) + } } + manifestFile.write(filenameList.toSorted().join("\n") + "\n") + copyAssetMetadataFile(manifestMetadataTemplateFile, manifestMetadataFile) + } +} + +// 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.pluginExportDirs.collect { it.path }) + + [project.ext.pluginExportFile.absolutePath, + "-gvh_disable", + "-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 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 { + File inputFile -> + return ( + inputFile.name.endsWith(project.ext.unityMetadataExtension) ? + [inputFile, + new File( + project.ext.pluginTemplateDir, + unversionedAssetFile( + new File(inputFile.absolutePath - + project.ext.pluginStagingAreaDir.absolutePath)).path)] : + [:]) + } + + description "Copy the plugin to release locations." + inputs.files files(project.ext.pluginExportFile, + pluginTemplateFilesMap.keySet()) + outputs.files files(project.ext.pluginReleaseFile, + 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"]) copy { - from file(exportPath) - into file(currentPluginPath) - rename ('plugin.unitypackage', currentPluginName) + from project.ext.pluginExportFile + 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.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) + } + println sprintf("Packaged to %s", project.ext.pluginReleaseFile) + } +} + +task gitAddReleaseFilesToStaging(type: Exec, dependsOn: releasePlugin) { + description "Run git to add release to the staging area." + commandLine "git", "add", "-A" +} + +/* + * Read the changes applied to the current version from the changelog. + * + * @returns Version summary in a multiline string. + */ +String readVersionSummaryFromChangelog() { + String versionSummary = "" + Boolean foundVersion = false + for (String line in project.ext.changelog.text.tokenize("\n")) { + String trimmedLine = line.trim() + if (line =~ /^# Version/) { + if (foundVersion) break + foundVersion = true + } + versionSummary += line.replace("#", "-") + "\n" } + return versionSummary +} + +/* + * Create a commit message for a release commit. + * + * @returns Commit message string. + */ +String createCommitMessage() { + return sprintf("Version %s\n\n%s", + project.ext.pluginVersion, + readVersionSummaryFromChangelog()) +} + +task gitCreateReleaseCommit(dependsOn: gitAddReleaseFilesToStaging) { + description "Run git to create a release commit." + doLast { - copy { - from file("${pluginProj}") - into file(currentPluginPath +"/exploded") - include "Assets/PlayServicesResolver/**/*" + def stdout = new ByteArrayOutputStream() + exec { + commandLine "git", "status", "-s" + ignoreExitValue true + standardOutput = stdout + } + if (!stdout.toString().isEmpty()) { + exec { + commandLine "git", "commit", "-a", "-m", createCommitMessage() + } } } - ext.remoteTaskPhase = 'postbuild' } -task build_unityPackage(dependsOn: 'copy_plugin') { - description "Top level task for building the unity package." - doLast { println "Packaging Complete!" } - ext.remoteTaskPhase = 'build' - dependsOn { - tasks.findAll { task -> task.name.startsWith('PackageSample') } +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", pluginVersion), "-m", + sprintf("%s\n\nDownload [here](%s)", + createCommitMessage(), + sprintf("https://github.com/googlesamples/" + + "unity-jar-resolver/raw/v%s/" + + "external-dependency-manager-%s.unitypackage", + project.ext.pluginVersion, + project.ext.pluginVersion)) +} + +// 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. +// - Linux library renaming (need to override LibraryPrefix to test) +// - Canonical filename renaming for versioned filenames +// - Change settings, restore default settings. +// - 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 +// - Resolve with: +// - Conflicting dependencies +// - FAT ABI vs. single ABI selection + +// TODO: iOS Resolver tests (on OSX only) +// - Import plugin test Cocoapods bootstrap +// - Pod specification, with reflection & XML, validate both are present in the +// set. +// - Workspace export (Unity 5.6 and above) +// - 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(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(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]) + +/* + * 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) } -task clean() { - doFirst { - delete 'build' - file("source").listFiles().each { f -> - if (file("$f/obj").exists()) { - delete "$f/obj" - } - if (file("$f/bin").exists()) { - delete "$f/bin" - } - } - } +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."), + [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." + delete project.ext.testDir } -defaultTasks = ['prebuild', 'build', 'postbuild'] -for (phase in defaultTasks) { - if (!tasks.findByName(phase)) { - def build_phase_name = 'Local ' + phase - tasks.create(name: phase, - action: { println(build_phase_name) }, - description: build_phase_name, +task cleanAll(type: Delete) { + description "Clean the build directory." + delete project.ext.buildDir +} + +project.defaultTasks = ["build", "test", "release", "clean"].collect { + topLevelTaskName -> + tasks.create(name: topLevelTaskName, + description: sprintf("Run all %s tasks", + topLevelTaskName), + type: Task, dependsOn: project.tasks.findAll { - task -> task.ext.has('remoteTaskPhase') && - task.ext.remoteTaskPhase == phase + 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 + } } } -project.defaultTasks = defaultTasks diff --git a/exploded/Assets/ExternalDependencyManager/Editor.meta b/exploded/Assets/ExternalDependencyManager/Editor.meta new file mode 100644 index 00000000..4ef59616 --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: b42aa8acaabecbf943da2892de5e6aeb +folderAsset: yes +timeCreated: 1448926516 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: 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/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll.meta new file mode 100644 index 00000000..a1398e9f --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.dll.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: fa49a85d4ba140a0ae21528ed12d174c +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.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.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/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll.meta b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/Google.VersionHandlerImpl.dll.meta new file mode 100644 index 00000000..7456068f --- /dev/null +++ b/exploded/Assets/ExternalDependencyManager/Editor/1.2.186/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/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/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.meta new file mode 100644 index 00000000..3babd47f --- /dev/null +++ b/exploded/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/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/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta b/exploded/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.pdb.meta new file mode 100644 index 00000000..0b461abd --- /dev/null +++ b/exploded/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/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.meta b/exploded/Assets/PlayServicesResolver/Editor.meta index 9f896272..457843c6 100644 --- a/exploded/Assets/PlayServicesResolver/Editor.meta +++ b/exploded/Assets/PlayServicesResolver/Editor.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c094ce2215f540818cd692689e38b4d5 +guid: e105e00cdce8456482d26b1fcd1ca47d folderAsset: yes timeCreated: 1448926516 licenseType: Pro diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.11.0.dll b/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.11.0.dll deleted file mode 100755 index e6694814..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.11.0.dll and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.11.0.dll.meta b/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.11.0.dll.meta deleted file mode 100644 index 038ea1bd..00000000 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.11.0.dll.meta +++ /dev/null @@ -1,24 +0,0 @@ -fileFormatVersion: 2 -guid: 8107eefe1657478a9fe3d79813b805c5 -labels: -- gvh_v1.2.11.0 -- gvh -- gvh_teditor -timeCreated: 1473798236 -licenseType: Pro -PluginImporter: - serializedVersion: 1 - iconMap: {} - executionOrder: {} - isPreloaded: 0 - platformData: - Any: - enabled: 0 - settings: {} - Editor: - enabled: 0 - settings: - DefaultValueInitialized: true - userData: - assetBundleName: - assetBundleVariant: diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.11.0.dll b/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.11.0.dll deleted file mode 100755 index 8d547230..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.11.0.dll and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.11.0.dll.meta b/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.11.0.dll.meta deleted file mode 100644 index 66c1138c..00000000 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.11.0.dll.meta +++ /dev/null @@ -1,24 +0,0 @@ -fileFormatVersion: 2 -guid: e1bd76e0ad124fa3843c2a4f4c0d6378 -labels: -- gvh_v1.2.11.0 -- gvh -- gvh_teditor -timeCreated: 1473798236 -licenseType: Pro -PluginImporter: - serializedVersion: 1 - iconMap: {} - executionOrder: {} - isPreloaded: 0 - platformData: - Any: - enabled: 0 - settings: {} - Editor: - enabled: 0 - settings: - DefaultValueInitialized: true - userData: - assetBundleName: - assetBundleVariant: diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler_v1.2.11.0.dll b/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler_v1.2.11.0.dll deleted file mode 100755 index 517911dc..00000000 Binary files a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler_v1.2.11.0.dll and /dev/null differ diff --git a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler_v1.2.11.0.dll.meta b/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler_v1.2.11.0.dll.meta deleted file mode 100644 index 45dac1c2..00000000 --- a/exploded/Assets/PlayServicesResolver/Editor/Google.VersionHandler_v1.2.11.0.dll.meta +++ /dev/null @@ -1,24 +0,0 @@ -fileFormatVersion: 2 -guid: 6331018ea8ec488b8a0659ab64245e64 -labels: -- gvh_v1.2.11.0 -- gvh -- gvh_teditor -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/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.11.0.txt b/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.11.0.txt deleted file mode 100644 index 5066079d..00000000 --- a/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.11.0.txt +++ /dev/null @@ -1,3 +0,0 @@ -Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.11.0.dll -Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.11.0.dll -Assets/PlayServicesResolver/Editor/Google.VersionHandler_v1.2.11.0.dll 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.137.0.txt.meta b/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.137.0.txt.meta new file mode 100644 index 00000000..af4c6c44 --- /dev/null +++ b/exploded/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/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.jar b/gradle/wrapper/gradle-wrapper.jar index 085a1cdc..3baa851b 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7b8a59e2..b129e8fb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Dec 01 12:11:27 PST 2015 +#Tue Aug 28 14:18:05 PDT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip diff --git a/gradlew b/gradlew index 91a7e269..27309d92 100755 --- a/gradlew +++ b/gradlew @@ -6,12 +6,30 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` diff --git a/gradlew.bat b/gradlew.bat index aec99730..f6d5974e 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,7 +46,7 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args 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.11.0.unitypackage b/play-services-resolver-1.2.11.0.unitypackage deleted file mode 100644 index 8e02e4e4..00000000 Binary files a/play-services-resolver-1.2.11.0.unitypackage and /dev/null differ diff --git a/plugin/Assets/ExternalDependencyManager.meta b/plugin/Assets/ExternalDependencyManager.meta new file mode 100644 index 00000000..1a04a8e0 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: e7f679112961a0f7f11fdb3f983aed77 +folderAsset: yes +timeCreated: 1448926447 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor.meta b/plugin/Assets/ExternalDependencyManager/Editor.meta new file mode 100644 index 00000000..4ef59616 --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: b42aa8acaabecbf943da2892de5e6aeb +folderAsset: yes +timeCreated: 1448926516 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: 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/ExternalDependencyManager/Editor/Google.IOSResolver.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.dll.mdb.meta new file mode 100644 index 00000000..3e39629c --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.IOSResolver.dll.mdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: adacdf2f31cf474c99788c9454063fed +labels: +- gvh_version-1.2.177 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.177/Google.IOSResolver.dll.mdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: 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/ExternalDependencyManager/Editor/Google.JarResolver.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.mdb.meta new file mode 100644 index 00000000..e5cd206a --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.mdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c613343662334614b65918fa6cf9c17e +labels: +- gvh_version-1.2.177 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.177/Google.JarResolver.dll.mdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.meta new file mode 100644 index 00000000..a1398e9f --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.JarResolver.dll.meta @@ -0,0 +1,35 @@ +fileFormatVersion: 2 +guid: fa49a85d4ba140a0ae21528ed12d174c +labels: +- gvh_version-1.2.186 +- gvhp_exportpath-ExternalDependencyManager/Editor/1.2.186/Google.JarResolver.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.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/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb.meta b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb.meta new file mode 100644 index 00000000..442c422b --- /dev/null +++ b/plugin/Assets/ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5855ffeab65945dc8f9cb3dc063f9eba +labels: +- gvh_version-1.2.177 +- gvhp_exportpath-ExternalDependencyManager/Editor/Google.VersionHandler.dll.mdb +- gvh +timeCreated: 1538009133 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: 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.meta b/plugin/Assets/PlayServicesResolver.meta index 761afa21..0a89788f 100644 --- a/plugin/Assets/PlayServicesResolver.meta +++ b/plugin/Assets/PlayServicesResolver.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 4f267ad2412843a28e3f4b9aa72f11a1 +guid: 333d24e71d12446ba5c77815e64b0ca8 folderAsset: yes timeCreated: 1448926447 licenseType: Pro diff --git a/plugin/Assets/PlayServicesResolver/Editor.meta b/plugin/Assets/PlayServicesResolver/Editor.meta index 9f896272..457843c6 100644 --- a/plugin/Assets/PlayServicesResolver/Editor.meta +++ b/plugin/Assets/PlayServicesResolver/Editor.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c094ce2215f540818cd692689e38b4d5 +guid: e105e00cdce8456482d26b1fcd1ca47d folderAsset: yes timeCreated: 1448926516 licenseType: Pro diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.meta b/plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.meta deleted file mode 100644 index bdf48e72..00000000 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.IOSResolver.dll.meta +++ /dev/null @@ -1,23 +0,0 @@ -fileFormatVersion: 2 -guid: 8107eefe1657478a9fe3d79813b805c5 -labels: -- gvh -- gvh_teditor -timeCreated: 1473798236 -licenseType: Pro -PluginImporter: - serializedVersion: 1 - iconMap: {} - executionOrder: {} - isPreloaded: 0 - platformData: - Any: - enabled: 0 - settings: {} - Editor: - enabled: 0 - settings: - DefaultValueInitialized: true - userData: - assetBundleName: - assetBundleVariant: diff --git a/plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.meta b/plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.meta deleted file mode 100644 index 9d3e7bf3..00000000 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.JarResolver.dll.meta +++ /dev/null @@ -1,23 +0,0 @@ -fileFormatVersion: 2 -guid: e1bd76e0ad124fa3843c2a4f4c0d6378 -labels: -- gvh -- gvh_teditor -timeCreated: 1473798236 -licenseType: Pro -PluginImporter: - serializedVersion: 1 - iconMap: {} - executionOrder: {} - isPreloaded: 0 - platformData: - Any: - enabled: 0 - settings: {} - Editor: - enabled: 0 - settings: - DefaultValueInitialized: true - 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 1798c465..00000000 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.PackageManager.dll.meta +++ /dev/null @@ -1,23 +0,0 @@ -fileFormatVersion: 2 -guid: 27cfaa6008d243edb8761f817ef6a8af -labels: -- gvh -- gvh_teditor -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.VersionHandler.dll.meta b/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta deleted file mode 100644 index 9ae39032..00000000 --- a/plugin/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta +++ /dev/null @@ -1,23 +0,0 @@ -fileFormatVersion: 2 -guid: 6331018ea8ec488b8a0659ab64245e64 -labels: -- gvh -- gvh_teditor -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/play-services-resolver.txt.meta b/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver.txt.meta index 3e2058cd..af4c6c44 100644 --- a/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver.txt.meta +++ b/plugin/Assets/PlayServicesResolver/Editor/play-services-resolver.txt.meta @@ -1,6 +1,7 @@ fileFormatVersion: 2 -guid: be5fee9360b8466f9a876d7250139318 +guid: ba6f911c6f9d4d9ea269756e9dafb641 labels: +- 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/ExternalDependencyManager/Editor/ResolutionRunner.cs b/sample/Assets/ExternalDependencyManager/Editor/ResolutionRunner.cs new file mode 100644 index 00000000..4c29c7aa --- /dev/null +++ b/sample/Assets/ExternalDependencyManager/Editor/ResolutionRunner.cs @@ -0,0 +1,71 @@ +// +// Copyright (C) 2017 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. +// + +/// +/// This class provides an example of how to do the following from an automated build environment: +/// * Enable the version handler +/// * Execute Android dependency resolution +/// * Perform a custom build step +/// +public class ResolutionRunner { + + /// + /// This method registers a set of methods to call when the Version Handler has enabled all + /// plugins in a project. + /// + public static void EnableResolver() { + Google.VersionHandler.UpdateCompleteMethods = new [] { + ":ResolutionRunner:ResolverEnabled" + }; + Google.VersionHandler.UpdateNow(); + } + + /// + /// This method is called when Version Handler has enabled all managed plugins in a project. + /// At this point it uses the Android Resolver (Google.JarResolver) component to perform + /// Android dependency resolution and finally execute a custom build step. + /// + public static void ResolverEnabled() { +#if UNITY_ANDROID + // Execute Android dependency resolution. + // NOTE: This is executed using reflection as the Android Resolver may not be loaded + // when this file is compiled by Unity. In the case the Android Resolver is initially + // disabled, the Version Handler enables the plugin when it initializes + // (see EnableResolver). + Google.VersionHandler.InvokeStaticMethod( + Google.VersionHandler.FindClass("Google.JarResolver", + "GooglePlayServices.PlayServicesResolver"), + "Resolve", args: null, + namedArgs: new System.Collections.Generic.Dictionary { { + "resolutionComplete", BuildYourApplication }, + }); +#else + BuildYourApplication(); +#endif // UNITY_ANDROID + } + + /// + /// This method is called after the Android dependency resolution is complete. + /// You should replace the implementation of this method to build / export your application. + /// + public static void BuildYourApplication() { + UnityEngine.Debug.Log("Ready to build"); + // TODO: Perform your build steps here. + + // TODO: You may want to change the exit code of the application if your build fails. + UnityEditor.EditorApplication.Exit(0); + } +} diff --git a/sample/Assets/ExternalDependencyManager/Editor/Resolver.cs b/sample/Assets/ExternalDependencyManager/Editor/Resolver.cs new file mode 100644 index 00000000..b00009f8 --- /dev/null +++ b/sample/Assets/ExternalDependencyManager/Editor/Resolver.cs @@ -0,0 +1,124 @@ +// +// Copyright (C) 2017 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.JarResolver +{ + using System; + using System.Collections.Generic; + using UnityEditor; + + /// + /// Resolver provides an interface to hide the reflection calls required to + /// setup the dependency tree. It is safe to use Resolver on any platform + /// since it will noop if Unity is not currently set to the Android platform. + /// + public static class Resolver + { + /// + /// When Resolver.CreateSupportInstance is called it will return a + /// ResolverImpl instance. You can then chain calls to the DependOn + /// method to setup your dependencies. DependOn will noop when not + /// on the Android platform. + /// + public class ResolverImpl + { + object _svcSupport; + + public ResolverImpl(object svcSupport) + { + _svcSupport = svcSupport; + } + + + /// + /// Adds a dependency to the project. + /// + /// This method should be called for + /// each library that is required. Transitive dependencies are processed + /// so only directly referenced libraries need to be added. + /// + /// The version string can be contain a trailing + to indicate " or greater". + /// Trailing 0s are implied. For example: + /// + /// 1.0 means only version 1.0, but + /// also matches 1.0.0. + /// + /// 1.2.3+ means version 1.2.3 or 1.2.4, etc. but not 1.3. + /// + /// + /// 0+ means any version. + /// + /// + /// LATEST means the only the latest version. + /// + /// + /// Group - the Group Id of the artifact + /// Artifact - Artifact Id + /// Version - the version constraint + /// Optional list of Android SDK package identifiers. + /// List of additional repository directories to search for + /// this artifact. + public ResolverImpl DependOn(string group, string artifact, string version, string[] packageIds = null, string[] repositories = null) + { + if (_svcSupport != null) { + Google.VersionHandler.InvokeInstanceMethod(_svcSupport, "DependOn", + new object[] { group, artifact, version }, + namedArgs: new Dictionary() + { + { "packageIds", packageIds }, + { "repositories", repositories } + }); + } + + return this; + } + } + + + /// + /// Creates an instance of PlayServicesSupport wrapped in a ResolverImpl instance. + /// This instance is used to add dependencies for the calling client. + /// + /// The instance. + /// Client name. Must be a valid filename. + /// This is used to uniquely identify + /// the calling client so that dependencies can be associated with a specific + /// client to help in resetting dependencies. + /// optional. Specifies which assembly the PlayServicesSupport + /// class should be loaded from. + public static ResolverImpl CreateSupportInstance(string clientName, string assemblyName = "Google.JarResolver") + { + // if we aren't on Android default to an empty shim + if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android) + return new ResolverImpl(null); + + // bail out with an empty instance if the PlayServicesSupport class isn't available + var playServicesSupport = Google.VersionHandler.FindClass(assemblyName, "Google.JarResolver.PlayServicesSupport"); + if (playServicesSupport == null) + return new ResolverImpl(null); + + // create a live instance of the PlayServicesSupport class and return a live ResolverImpl wrapping it + var svcSupport = Google.VersionHandler.InvokeStaticMethod(playServicesSupport, "CreateInstance", + new object[] { + clientName, + EditorPrefs.GetString( "AndroidSdkRoot" ), + "ProjectSettings" + } ); + + return new ResolverImpl(svcSupport); + } + } +} \ No newline at end of file diff --git a/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.cs b/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.cs new file mode 100644 index 00000000..06b7515f --- /dev/null +++ b/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.cs @@ -0,0 +1,138 @@ +// +// 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. +// + +using System; +using System.Collections.Generic; +using UnityEditor; + +/// WARNING: This method of dependency specification is deprecated, we recommend you use +/// XML dependency specification instead. See SampleAndroidDeps.xml for an example of how to +/// add dependencies using XML files. +/// +/// Sample dependencies file. +/// Change the class name and dependencies as required by your project, then +/// save the file in a folder named Editor (which can be a sub-folder of your plugin). +/// There can be multiple dependency files like this one per project, the resolver will combine +/// them and process all of them at once. +[InitializeOnLoad] +public class SampleDependencies : AssetPostprocessor { + /// Instance of the PlayServicesSupport resolver + public static object svcSupport; + + /// Initializes static members of the class. + static SampleDependencies() { + // + // + // NOTE: + // + // UNCOMMENT THIS CALL TO MAKE THE DEPENDENCIES BE REGISTERED. + // THIS FILE IS ONLY A SAMPLE!! + // + // RegisterDependencies(); + // + } + + /// + /// Registers the dependencies needed by this plugin. + /// + public static void RegisterDependencies() { +#if UNITY_ANDROID + RegisterAndroidDependencies(); +#elif UNITY_IOS + RegisterIOSDependencies(); +#endif + } + + /// + /// Registers the android dependencies. + /// + public static void RegisterAndroidDependencies() { + // Setup the resolver using reflection as the module may not be + // available at compile time. + Type playServicesSupport = Google.VersionHandler.FindClass( + "Google.JarResolver", "Google.JarResolver.PlayServicesSupport"); + if (playServicesSupport == null) { + return; + } + svcSupport = svcSupport ?? Google.VersionHandler.InvokeStaticMethod( + playServicesSupport, "CreateInstance", + new object[] { + "GooglePlayGames", + null /* No longer required. */, + "ProjectSettings" + }); + + // For example to depend on play-services-games version 9.6.0 you need to specify the + // package, artifact, and version as well as the packageId from the SDK manager in case + // a newer version needs to be downloaded to build. + Google.VersionHandler.InvokeInstanceMethod( + svcSupport, "DependOn", + new object[] { + "com.google.android.gms", + "play-services-games", + "9.6.0" + }, + namedArgs: new Dictionary() { + {"packageIds", new string[] { "extra-google-m2repository" } } + }); + + // This example gets the com.android.support.support-v4 library, version 23.1 or greater. + // notice it is in a different package than the play-services libraries. + Google.VersionHandler.InvokeInstanceMethod( + svcSupport, "DependOn", + new object[] { "com.android.support", "support-v4", "23.1+" }, + namedArgs: new Dictionary() { + {"packageIds", new string[] { "extra-android-m2repository" } } + }); + } + + /// + /// Registers the IOS dependencies. + /// + public static void RegisterIOSDependencies() { + // Setup the resolver using reflection as the module may not be + // available at compile time. + Type iosResolver = Google.VersionHandler.FindClass("Google.IOSResolver", + "Google.IOSResolver"); + if (iosResolver == null) { + return; + } + + // Dependencies for iOS are added by referring to CocoaPods. The libraries and frameworks + // are added to the Unity project, so they will automatically be included. + // + // This example adds the GooglePlayGames pod, version 5.0 or greater, disabling bitcode + // generation. + Google.VersionHandler.InvokeStaticMethod( + iosResolver, "AddPod", new object[] { "GooglePlayGames" }, + namedArgs: new Dictionary() { + { "version", "5.0+" }, + { "bitcodeEnabled", false }, + }); + } + + // Handle delayed loading of the dependency resolvers. + private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, + string[] movedAssets, string[] movedFromPath) { + foreach (string asset in importedAssets) { + if (asset.Contains("IOSResolver") || + asset.Contains("JarResolver")) { + RegisterDependencies(); + break; + } + } + } +} diff --git a/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.xml b/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.xml new file mode 100644 index 00000000..97c27dec --- /dev/null +++ b/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.xml @@ -0,0 +1,94 @@ + + + + + + + https://repo.maven.apache.org/maven2 + + + + + + extra-google-m2repository + + + + https://maven.google.com + + + + + + + + + https://cocoapods.mycompany.com/Specs + + + + + + https://github.com/CocoaPods/Specs + + + + diff --git a/sample/Assets/ExternalDependencyManager/Editor/SampleRegistries.xml b/sample/Assets/ExternalDependencyManager/Editor/SampleRegistries.xml new file mode 100644 index 00000000..d9d60588 --- /dev/null +++ b/sample/Assets/ExternalDependencyManager/Editor/SampleRegistries.xml @@ -0,0 +1,46 @@ + + + + + + + + + com.mycompany + + + diff --git a/sample/Assets/PlayServicesResolver/Editor/SampleDependencies.cs b/sample/Assets/PlayServicesResolver/Editor/SampleDependencies.cs deleted file mode 100644 index 50879df9..00000000 --- a/sample/Assets/PlayServicesResolver/Editor/SampleDependencies.cs +++ /dev/null @@ -1,143 +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. -// - -using System; -using System.Collections.Generic; -using UnityEditor; - -/// Sample dependencies file. Change the class name and dependencies as required by your project, then -/// save the file in a folder named Editor (which can be a sub-folder of your plugin). -/// There can be multiple dependency files like this one per project, the resolver will combine them and process all -/// of them at once. -[InitializeOnLoad] -public class SampleDependencies : AssetPostprocessor { -#if UNITY_ANDROID - /// Instance of the PlayServicesSupport resolver - public static object svcSupport; -#endif // UNITY_ANDROID - - /// Initializes static members of the class. - static SampleDependencies() { - - // - // - // NOTE: - // - // UNCOMMENT THIS CALL TO MAKE THE DEPENDENCIES BE REGISTERED. - // THIS FILE IS ONLY A SAMPLE!! - // - // RegisterDependencies(); - // - } - - - /// - /// Registers the dependencies needed by this plugin. - /// - public static void RegisterDependencies() { -#if UNITY_ANDROID - RegisterAndroidDependencies(); -#elif UNITY_IOS - RegisterIOSDependencies(); -#endif - } - - /// - /// Registers the android dependencies. - /// - public static void RegisterAndroidDependencies() { - - // Setup the resolver using reflection as the module may not be - // available at compile time. - Type playServicesSupport = Google.VersionHandler.FindClass( - "Google.JarResolver", "Google.JarResolver.PlayServicesSupport"); - if (playServicesSupport == null) { - return; - } - svcSupport = svcSupport ?? Google.VersionHandler.InvokeStaticMethod( - playServicesSupport, "CreateInstance", - new object[] { - "GooglePlayGames", - EditorPrefs.GetString("AndroidSdkRoot"), - "ProjectSettings" - }); - - // For example to depend on play-services-games version 9.6.0 you need to specify the - // package, artifact, and version as well as the packageId from the SDK manager in case - // a newer version needs to be downloaded to build. - - Google.VersionHandler.InvokeInstanceMethod( - svcSupport, "DependOn", - new object[] { - "com.google.android.gms", - "play-services-games", - "9.6.0" }, - namedArgs: new Dictionary() { - {"packageIds", new string[] { "extra-google-m2repository" } } - }); - - // This example gets the com.android.support.support-v4 library, version 23.1 or greater. - // notice it is in a different package than the play-services libraries. - - Google.VersionHandler.InvokeInstanceMethod( - svcSupport, "DependOn", - new object[] { "com.android.support", "support-v4", "23.1+" }, - namedArgs: new Dictionary() { - {"packageIds", new string[] { "extra-android-m2repository" } } - }); - } - - /// - /// Registers the IOS dependencies. - /// - public static void RegisterIOSDependencies() { - - // Setup the resolver using reflection as the module may not be - // available at compile time. - Type iosResolver = Google.VersionHandler.FindClass( - "Google.IOSResolver", "Google.IOSResolver"); - if (iosResolver == null) { - return; - } - - // Dependencies for iOS are added by referring to CocoaPods. The libraries and frameworkds are - // and added to the Unity project, so they will automatically be included. - // - // This example add the GooglePlayGames pod, version 5.0 or greater, disabling bitcode generation. - - Google.VersionHandler.InvokeStaticMethod( - iosResolver, "AddPod", - new object[] { "GooglePlayGames" }, - namedArgs: new Dictionary() { - { "version", "5.0+" }, - { "bitcodeEnabled", false }, - }); - } - - // Handle delayed loading of the dependency resolvers. - private static void OnPostprocessAllAssets( - string[] importedAssets, string[] deletedAssets, - string[] movedAssets, string[] movedFromPath) { - foreach (string asset in importedAssets) { - if (asset.Contains("IOSResolver") || - asset.Contains("JarResolver")) { - RegisterDependencies(); - break; - } - } - } -} - 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/AndroidResolver/AndroidResolver.csproj b/source/AndroidResolver/AndroidResolver.csproj new file mode 100644 index 00000000..08059780 --- /dev/null +++ b/source/AndroidResolver/AndroidResolver.csproj @@ -0,0 +1,103 @@ + + + + Debug + AnyCPU + 12.0.0 + 2.0 + {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A} + Library + PlayServicesResolver + Google.JarResolver + 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Google.JarResolver\Dependency.cs + + + Google.JarResolver\PlayServicesSupport.cs + + + + + + scripts\gradle-template.zip + + + scripts\download_artifacts.gradle + + + scripts\settings.gradle + + + + + {5378B37A-887E-49ED-A8AE-42FA843AA9DC} + VersionHandler + + + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + VersionHandlerImpl + + + diff --git a/source/PlayServicesResolver/Properties/AssemblyInfo.cs b/source/AndroidResolver/Properties/AssemblyInfo.cs similarity index 89% rename from source/PlayServicesResolver/Properties/AssemblyInfo.cs rename to source/AndroidResolver/Properties/AssemblyInfo.cs index d496a3db..2d9fbd82 100644 --- a/source/PlayServicesResolver/Properties/AssemblyInfo.cs +++ b/source/AndroidResolver/Properties/AssemblyInfo.cs @@ -15,6 +15,7 @@ // 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. @@ -51,3 +52,9 @@ // See the License for the specific language governing permissions and // limitations under the License. // [assembly: AssemblyKeyFile("")] + +// 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/AndroidResolver/scripts/download_artifacts.gradle b/source/AndroidResolver/scripts/download_artifacts.gradle new file mode 100644 index 00000000..7d99ec35 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts.gradle @@ -0,0 +1,2725 @@ +/* + * Copyright 2017 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 Gradle script performs the following: +- Adds Android maven repositories to search locations for artifacts. +- Adds user specified maven repositories to search locations for artifacts. +- Copies specified set of maven artifacts to the specified output directory. +- Displays the set of files copied into the output directory. +- Displays any packages that were not found. + +./gradlew -b """ + project.buildscript.sourceFile + """ \\ + \"-PMAVEN_REPOS=[semicolon separated list of repo URIs]\" \\ + \"-PPACKAGES_TO_COPY=[semicolon separated list of maven artifacts]\" \\ + -PTARGET_DIR=[target directory] + +ANDROID_HOME (optional env var, system property or project property): + Optional environment variable, system property or project project that + specifies the install location of the Android SDK. +MAVEN_REPOS (optional project property): + Optional property which adds to the list of Maven repositories to search. + This is a semicolon separated list of URIs e.g + \"-PMAVEN_REPOS=http://some.repos.com;file:///some/other/path\" + Since this property semicolon separated it needs to be quoted correctly + when specified via some command line shells. +USE_MAVEN_LOCAL_REPO (optional project property): + Optional property which, when not set to 1, disables the implicit use of local + maven repositories (see mavenLocal() in the gradle docs). + 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 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 + TARGET_DIR. Specified artifacts that are not copied to the target directory + are logged to the standard output stream. + e.g + \"-PPACKAGES_TO_COPY=com.android.support:support-compat:26.0.1;\ +com.android.support:support-core-utils:26.0.1\" + Since this property semicolon separated it needs to be quoted correctly + when specified via some command line shells. +TARGET_DIR (required project property): + Directory to copy artifacts to. + e.g -PTARGET_DIR=some/directory/to/copy/to +""" + +buildscript { + repositories { + mavenLocal() + mavenCentral() + 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 + // Configuration used to aggregate the set of packages we'll copy. + copyPackagesConfig +} + +// Class which splits a version string into components. +@ToString(includeNames=true, includeFields=true) +public class Version implements Comparable { + // Version string to parse. + public String version = "" + + /* + * Construct a version class from a string. + */ + public Version(String version = null) { + this.version = version ? version : "" + } + + /* + * Split a version string into components that can be compared. + * + * @returns A list of components that can be compared either as integer + * values or strings. + */ + public List getComparableComponents() { + // Split into groups of numeric and non-numeric strings. + List components = [] + if (version) { + boolean previousIsInteger = false + String component = "" + version.each { it -> + boolean currentIsInteger = it.isInteger() + // Determine whether the current character is the same type as the + // previous character. + boolean currentTypeSameAsPrevious = + (currentIsInteger == previousIsInteger) + if (!component.isEmpty() && !currentTypeSameAsPrevious) { + components.add(component) + component = "" + } + previousIsInteger = currentIsInteger + component += it + } + if (!component.isEmpty()) components.add(component) + } + return components + } + + /* + * Split a version string into period separated components. + * + * @returns List of version number components. + */ + public List getComponents() { + return version ? version.tokenize('.') : [] + } + + /* + * Expand wildcards in this version number to the minimum absolute value. + * + * @returns Expanded version number. + */ + public Version getMinimumValue() { + List versionNumberComponents = [] + for (String component in components) { + if (component.endsWith("+")) { + // Strip the + from the version number and replace with 0 + String nonWildcardComponent = + component.substring(0, component.size() - 1) + versionNumberComponents.add( + nonWildcardComponent ? nonWildcardComponent : "0") + break + } + versionNumberComponents.add(component) + } + return new Version(version: versionNumberComponents.join(".")) + } + + /* + * Expand wildcards in this version number to the maximum absolute value. + * + * @returns Expanded version number. + */ + public Version getMaximumValue() { + List versionNumberComponents = [] + for (String component in components) { + if (component.endsWith("+")) { + // Strip the + from the version number and subtract the existing version + // number, if it can be parsed, from Integer.MAX_VALUE. This ensures + // 1.2.+ is a larger number than 1.2.1+ since 1.2.+ is less restrictive + // than 1.2.1+. + String nonWildcardComponent = + component.substring(0, component.size() - 1) + int valueToSubtract = nonWildcardComponent.isInteger() ? + (nonWildcardComponent as int) + 1 : 0 + versionNumberComponents.add((Integer.MAX_VALUE - + valueToSubtract).toString()) + break + } + versionNumberComponents.add(component) + } + return new Version(version: versionNumberComponents.join(".")) + } + + /* + * Returns whether the version contains any wildcards. + * + * @returns true if the version contains wildcards, false otherwise. + */ + public boolean getHasWildcards() { + return version.contains("+") + } + + /* + * Compare with another version object. + * + * @param otherObject version object to compare with. + * + * @returns -1 if other is less than this version, 0 if they're the + * same, 1 if other is greater than this version. + */ + public int compareTo(Object otherObject) { + Version other = otherObject as Version + List thisComponents = comparableComponents + List otherComponents = other.comparableComponents + int componentsToCompare = Math.min(thisComponents.size(), + otherComponents.size()) + for (int i = 0; i < componentsToCompare; ++i) { + String thisComponent = thisComponents[i] + String otherComponent = otherComponents[i] + int result = + (thisComponent.isInteger() && otherComponent.isInteger()) ? + (thisComponent as BigInteger) <=> (otherComponent as BigInteger) : + thisComponent <=> otherComponent + if (result != 0) return result + } + return thisComponents.size() <=> otherComponents.size() + } + + /* + * Sort a list of versions + * + * @param versions Versions to sort. + * + * @returns A list of versions ordered by oldest to most recent version. + */ + public static List sort(Iterable versions) { + return versions.collect { it }.sort(false) + } + + /* + * Modify the version expression such that it has looser requirements + * accepting any patch, revision then finally version starting at the + * current version. + * + * @returns Loosened version if possible or the version. + */ + public Version loosen() { + List loosenedComponents = components.clone() + int lastComponentIndex = loosenedComponents.size() - 1 + if (lastComponentIndex >= 0) { + String lastComponent = loosenedComponents[lastComponentIndex] + if (lastComponent == "+") { + lastComponent = + (lastComponentIndex > 0 ? + loosenedComponents[lastComponentIndex - 1] : "") + "+" + lastComponentIndex-- + } else if (lastComponent.endsWith("+")) { + lastComponent = "+" + } else { + lastComponent = lastComponent + "+" + } + loosenedComponents = + lastComponentIndex > 0 ? + loosenedComponents[0 .. lastComponentIndex - 1] : [] + if (lastComponent) loosenedComponents.add(lastComponent) + } + return new Version(loosenedComponents ? loosenedComponents.join(".") : "+") + } +} + +// Type of version expression match. +public enum VersionExpressionMatchType { + NONE, // No match + RANGE, // Version in a range of versions. + ABSOLUTE, // Absolute version expression specified. + MINIMUM, // Minimum version of a min-version expression. +} + +// Class which holds a version range. +@AutoClone +@EqualsAndHashCode(includeFields=true) +@ToString(includeNames=true, includeFields=true) +public class VersionRange { + // Parsed version expression. + public String versionExpression = "" + // Minimum version parsed from a version expression. + public String minimum = "0" + // Maximum version parsed from a version expression. + public String maximum = "+" + // Type of version expression parsed. + public VersionExpressionMatchType matchType = + VersionExpressionMatchType.NONE + + /* + * Get the minimum and maximum version from a maven / ivy version expression. + * This does not distiguish between exclusive and inclusive version ranges. + * i.e [1.2.3,] is treated the same as (1.2.3,]. + * + * @param versionExpression Version expression to parse. + * + * @returns VersionRange instance that contains the parsed data. + */ + public static VersionRange fromExpression(String versionExpression) { + // Match Maven / Ivy version range expressions like: + // [1.2.3,4.5.6] + // [1.2.3,4.5.6) + // (1.2.3,4.5.6) + // (1.2.3,] + // [,4.5.6) + def versionRangeMatch = (versionExpression =~ + /^([\[\(])([^,]*),\s*([^\]\)]*)([\)\]])$/) + // Match absolute Maven / Ivy version expressions like [1.2.3]. + def absoluteVersionMatch = (versionExpression =~ /^\[([^\]]+)\]$/) + // Match min value Maven version expressions (i.e which don't start with + // [ or (. + def minVersionMatch = (versionExpression =~ /^([^\[\(]+)$/) + VersionRange range = new VersionRange( + versionExpression: versionExpression, minimum: "0", maximum: "+", + matchType: VersionExpressionMatchType.NONE) + String minMatchedVersion = "" + String maxMatchedVersion = "" + if (versionRangeMatch.matches()) { + range.matchType = VersionExpressionMatchType.RANGE + minMatchedVersion = versionRangeMatch.group(2).trim() + if (minMatchedVersion) { + minMatchedVersion = + (new Version(minMatchedVersion)).minimumValue.version + } + maxMatchedVersion = versionRangeMatch.group(3).trim() + if (maxMatchedVersion) { + maxMatchedVersion = + (new Version(maxMatchedVersion)).maximumValue.version + } + } else if (absoluteVersionMatch.matches()) { + range.matchType = VersionExpressionMatchType.ABSOLUTE + minMatchedVersion = absoluteVersionMatch.group(1).trim() + if (minMatchedVersion) { + minMatchedVersion = + (new Version(minMatchedVersion)).minimumValue.version + } + maxMatchedVersion = minMatchedVersion + } else if (minVersionMatch.matches()) { + range.matchType = VersionExpressionMatchType.MINIMUM + minMatchedVersion = minVersionMatch.group(1).trim() + } else { + range.matchType = VersionExpressionMatchType.NONE + } + if (!minMatchedVersion.isEmpty()) range.minimum = minMatchedVersion + if (!maxMatchedVersion.isEmpty()) range.maximum = maxMatchedVersion + return range + } + + /* + * Get the version range expression. + * + * @returns Version expression. + */ + String getExpression() { + switch (matchType) { + case VersionExpressionMatchType.NONE: + return versionExpression + case VersionExpressionMatchType.RANGE: + return sprintf("[%s,%s]", minimum, maximum) + case VersionExpressionMatchType.ABSOLUTE: + return sprintf("[%s]", minimum) + case VersionExpressionMatchType.MINIMUM: + return sprintf("%s", minimum) + } + } + + /* + * Get the minimum version as a Version object. + * + * @return Version object. + */ + Version getMinimumVersion() { + return new Version(version: minimum) + } + + /* + * Get the maximum version as a Version object. + * + * @return Version object. + */ + Version getMaximumVersion() { + return new Version(version: maximum) + } + + /* + * Get the maximum absolute version number from this object replacing the + * wildcard character "+" from the version expression with Integer.MAX_VALUE. + * + * This facilitates sorting version expressions with wildcard values after + * non-wildcard card values. For example, 1.2.+ will be ordered after 1.2.0. + * + * @return Returns a version string with the wildcard component replaced by + * Integer.MAX_VALUE. + */ + Version getMaximumVersionOfRange() { + return (matchType == VersionExpressionMatchType.RANGE ? + maximumVersion : minimumVersion).maximumValue + } + + /* + * Determine whether a version is within the range. + * + * @param version Version to check against this range. + * + * @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 + case VersionExpressionMatchType.RANGE: + return version.maximumValue >= minimumVersion.minimumValue && + version.minimumValue <= maximumVersion.maximumValue + case VersionExpressionMatchType.ABSOLUTE: + return version == minimumVersion + case VersionExpressionMatchType.MINIMUM: + return version >= minimumVersion.minimumValue + } + return false + } +} + +// Components of a package spec. +@AutoClone +@EqualsAndHashCode(includeFields=true) +@ToString(includeNames=true, includeFields=true) +public class PackageSpecifier implements Comparable { + // Group component of a maven package. + public String group = "" + // Artifact component of a maven package. + public String artifact = "" + // Version expression of a maven package. + public String versionExpression = "" + // Classifier of the artifact. + public String classifier = "" + // Type of the artifact. + public String artifactType = "" + + /* + * Extract the components of a package specifier string. + * + * @param packageSpecifier Package specification. + * 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, 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 += ([""] * Math.min(5, (5 - components.size()))) + + return new PackageSpecifier( + group: components[0], + artifact: components[1], + versionExpression: components[2] ? (VersionRange.fromExpression( + components[2])).expression : "", + artifactType: components[3], + classifier: components[4]) + } + + /* + * Convert a list of package specifier strings to PackageSpecifier instances. + * + * @param packageSpecifierStrings List of strings to convert. + * + * @returns PackageSpecifier instances. + */ + public static List fromStrings( + Iterable packageSpecifierStrings) { + return packageSpecifierStrings.collect { fromString(it) } + } + + /* + * Convert a Gradle ModuleComponentSelector to a PackageSpecifier. + * + * @param selector Selector to convert. + * + * @returns PackageSpecifier instance. + */ + public static PackageSpecifier fromModuleComponentSelector( + ModuleComponentSelector selector) { + return selector.with { + return new PackageSpecifier( + group: group, + artifact: module, + versionExpression: (version ? + (VersionRange.fromExpression(version)).expression : "")) + } + } + + /* + * Convert a Gradle ResolvedArtifact to a PackageSpecifier. + * + * @param resolvedArtifact ResolvedArtifact to convert. + * + * @returns PackageSpecifier instance. + */ + public static PackageSpecifier fromResolvedArtifact( + ResolvedArtifact resolvedArtifact) { + return resolvedArtifact.with { + PackageSpecifier pkg = fromModuleVersionIdentifier(moduleVersion.id) + pkg.artifactType = type + pkg.classifier = classifier + return pkg + } + } + + /* + * Convert a Gradle ModuleVersionIdentifier to a PackageSpecifier. + * + * @param moduleVersionIdentifier ModuleVersionIdentifier to convert. + * + * @returns PackageSpecifier instance. + */ + public static PackageSpecifier fromModuleVersionIdentifier( + ModuleVersionIdentifier moduleVersionIdentifier) { + return moduleVersionIdentifier.with { + return new PackageSpecifier( + group: group, + artifact: name, + versionExpression: (version ? + (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) : "")) + } + } + + /* + * Sort a list of package specifiers by version. + * + * @param packages Package specifiers to sort. + * + * @returns A list of package specifiers order by oldest to most recent + * version. + */ + public static List sortByVersion( + Iterable packages) { + return packages.collect { it }.sort(false) { lhs, rhs -> + (lhs.versionRange.maximumVersionOfRange <=> + rhs.versionRange.maximumVersionOfRange) + } + } + + /* + * Get the range from the version. + * + * @returns Version range parsed from version component of this class or an + * empty string if no version is set. + */ + public VersionRange getVersionRange() { + return VersionRange.fromExpression(versionExpression) + } + + /* + * Compare with another PackageSpecifier object. + * + * @param otherObject PackageSpecifier object to compare with. + * + * @returns -1 if other is less than this version, 0 if they're the + * same, 1 if other is greater than this version. + */ + public int compareTo(Object otherObject) { + PackageSpecifier other = otherObject as PackageSpecifier + List compareResults = [ + group <=> other.group, + artifact <=> other.artifact, + versionRange?.minimumVersion <=> other?.versionRange?.minimumVersion, + versionRange?.maximumVersion <=> other?.versionRange?.maximumVersion, + artifactType <=> other.artifactType] + for (int itemComparison in compareResults) { + if (itemComparison != 0) return itemComparison + } + return 0 + } + + /* + * Convert to a list of components. + * + * @returns String list of components in the form + * [group, artifact, versionAndArtifactType]. Missing items are not returned + * in the list. + * For example, without a versionExpression this returns [group, artifact]. + */ + private List getComponentStrings() { + List components = [] + if (group) { + components.add(group) + if (artifact) { + components.add(artifact) + if (versionExpression) { + if (artifactType) { + if (classifier) { + components.add(versionExpression + ':' + classifier + "@" + artifactType) + } else { + components.add(versionExpression + "@" + artifactType) + } + } else { + components.add(versionExpression) + } + } + } + } + return components + } + + /* + * Get group and artifact components as a string list. + * + * @returns [group, artifact] if they are present, empty list otherwise. + */ + private List getGroupArtifact() { + List components = componentStrings + return components.size() >= 2 ? components[0..1] : [] + } + + /* + * Convert to a colon separated string. + * + * @returns Colon separated string. + */ + public String getSpecString() { + return componentStrings.join(":") + } + + /* + * Get group / artifact tuple as a colon separated string. + * + * @returns Colon separated string. + */ + public String getGroupArtifactString() { + return groupArtifact.join(":") + } + + /* + * Convert to a valid filename. + * + * @returns Filename string. + */ + public String getFilename() { + List hypenSeparatedComponents = [] + String dotSeparatedGroupArtifact = groupArtifact.join(".") + if (dotSeparatedGroupArtifact) { + hypenSeparatedComponents += [dotSeparatedGroupArtifact] + } + if (versionExpression) { + hypenSeparatedComponents += [versionExpression] + } + if (classifier) { + hypenSeparatedComponents += [classifier] + } + String filename = hypenSeparatedComponents.join("-") + if (artifactType) { + filename += "." + (artifactType == "srcaar" ? "aar" : artifactType) + } + return filename + } + + /* + * From a set of package specifiers create a map of lists of package + * specifiers indexed by group:artifact strings (e.g + * com.example:my-package:1.2.3@aar would + * become ret[com.example:my-package] = ["com.example:my-package:1.2.3.@aar"] + * + * @param packages List of package specifier strings. + * + * @returns A map of package specifier lists by group:name strings. + */ + public static Map> byGroupArtifact( + Iterable packages) { + Map> packagesByGroupArtifact = [:] + packages.each { + String groupArtifact = it.groupArtifactString + if (groupArtifact) { + List packageSpecs = packagesByGroupArtifact.get( + groupArtifact, []) + packageSpecs.add(it) + packagesByGroupArtifact[groupArtifact] = packageSpecs + } + } + return packagesByGroupArtifact + } + + /* + * From a set of package specifiers create a map of package names indexed + * by group:artifact strings (e.g, com.example:my-package:1.2.3@aar would + * become ret[com.example:my-package] = "com.example:my-package:1.2.3@aar") + * Package specifiers without at least a group and artifact are ignored. + * + * If multiple versions of the same package are specified, the highest version + * is returned in the map. + * + * @param packages List of package specifiers. + * + * @returns A map of package specifiers by group:name strings. + */ + public static Map mostRecentByGroupArtifact( + Iterable packages) { + return PackageSpecifier.byGroupArtifact(packages).collectEntries { + String groupArtifact, List packageSpecs -> + [groupArtifact, PackageSpecifier.sortByVersion(packageSpecs)[-1]] + } + } + + /* + * Get a list of specification strings from PackageSpecifier instances. + * + * @param packages PackageSpecifier instances. + * + * @returns List of package spec strings. + */ + public static List specStrings(Iterable packages) { + return packages.collect { it.specString } + } +} + +// 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. + * + * @param pkg Package spec to query. + * + * @returns 0 if the package isn't version locked, index value greater than 0 + * if the package is added to the lockedPackages list or an index value lesser + * than 0 if the package is part of a version locked set but is at a non-version + * locked version. For example, if a:b.c:1.0 is version locked and a:b.c:2.0 + * is not version locked - 1.0 & 2.0 are arbitrary versions - this will return a + * positive index value for a:b.c:1.0 and negative value for a:b.c:2.0. + */ +int getVersionLockedPackageIndex(PackageSpecifier pkg) { + // Packages that should be version-locked. + for (def indexAndRegex in [ + // * com.google.android.gms.* packages are released a single set that + // typically are not compatible between revisions. e.g If a user + // depends upon play-services-games:9.8.0 they'll also require + // play-services-base:9.8.0 etc. + // * com.google.firebase.* packages are versioned in the same way as + // com.google.android.gms.* with dependencies upon the gms + // (Play Services) components. and need to be pinned to the same + // com.google.android.gms.* packages. + ~/^com\.google\.(android\.gms|firebase):.*/, + // com.android.support packages all need to be included at the same + // revision. + ~/^com\.android\.support:.*/, + // Package group used to test this script. + ~/^org\.test\.psr\.locked:.*/, + ].withIndex()) { + def (versionLockedRegex, index) = indexAndRegex + String specString = pkg.specString + if (!(specString ==~ versionLockedRegex)) continue + // com.google.firebase.* packages that end with -unity$ are shipped + // separately so they are *not* locked to Google Play services and + // Firebase packages. + 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)$/ && + pkg.versionExpression) { + String majorVersion = + VersionRange.fromExpression( + (new Version(version: pkg.versionRange.minimum)).components[0]). + maximumVersionOfRange.version + if (majorVersion.isInteger() && Integer.parseInt(majorVersion) >= 15) { + return -(index + 1) + } + } + return index + 1 + } + return 0 +} + +/* + * If the specified package is version-locked add it to the appropriate set. + * + * @param packageSpecifier Package to add to a set if is version-locked. + * A version-locked package is determined by a return value of >= 0 by + * getVersionLockedPackageIndex(). + * @param lockedPackages Map of sets of version-locked packages. Each set + * is indexed by the value returned by getVersionLockedPackageIndex(). + * @param blacklist Set of package set indexes that should not be used. + * This is utilized when a non-version locked package exists in the set of + * dependencies for a package and therefore no packages in the set should be + * locked to a specific version. + * + * @returns 0 if the package isn't version locked, index value greater than 0 + * if the package is added to the lockedPackages list or an index value lesser + * than 0 if the package is part of a version locked set but is at a non-version + * locked version. For example, if a:b.c:1.0 is version locked and a:b.c:2.0 + * is not version locked - 1.0 & 2.0 are arbitrary versions - this will return a + * positive index value for a:b.c:1.0 and negative value for a:b.c:2.0. + */ +int addVersionLockedPackageToSet( + PackageSpecifier packageSpecifier, + Map> lockedPackages, + Set blacklist) { + int index = getVersionLockedPackageIndex(packageSpecifier) + if (index in blacklist) index = -index + Set lockedPackageSet = lockedPackages.get(index, [].toSet()) + lockedPackageSet.add(packageSpecifier) + lockedPackages[index] = lockedPackageSet + return index +} + +/* + * Given a set of package specs, generate a blacklist of version locked package + * set indexes. + * + * @param packages List of package specifications to scan for + * version-locked package groups that should be blacklisted. + * + * @returns Set of blacklisted package set indexes for groups of packages + * that should not be version-locked. + */ +Set createVersionLockedSetBlacklist( + Iterable packages) { + Set blacklist = [].toSet() + packages.each { pkg -> + int index = getVersionLockedPackageIndex(pkg) + if (index < 0) blacklist.add(-index) + } + return blacklist +} + +/* + * Generate a map of the most recent version-locked packages each set. + * + * @param lockedPackages Sets of version-locked packages indexed by an + * arbitrary number for grouping. + * + * @returns List of packages. + */ +Iterable mostRecentVersionLockedPackagesFromSet( + Map> lockedPackages) { + List versionLockedPackages = [] + lockedPackages.findAll({ it.key > 0 }).values().each { + Iterable packages -> + List sortedPackages = + PackageSpecifier.sortByVersion(packages) + String highestVersionExpression = sortedPackages[-1].versionExpression + sortedPackages.each { PackageSpecifier pkg -> + PackageSpecifier lockedPkg = pkg.clone() + lockedPkg.versionExpression = highestVersionExpression + versionLockedPackages.add(lockedPkg) + } + } + return versionLockedPackages.sort() +} + +/* + * Lock all packages in the specified set to the same version if they should + * be version-locked. + * + * @param packages Packages to search for version locked packages. + * + * @returns Potentially a subset of `packages` with version locking applied. + */ +Iterable versionLockPackages( + Iterable packages) { + // Build a sets of packages that should be version locked and bucket into + // version locked groups. + Map> versionLockedPackageSets = [:] + Set versionLockedSetBlacklist = + createVersionLockedSetBlacklist(packages) + packages.each { + addVersionLockedPackageToSet(it, versionLockedPackageSets, + versionLockedSetBlacklist) + } + // Lock each group of version locked packages to the most recent version. + return mostRecentVersionLockedPackagesFromSet(versionLockedPackageSets) +} + +/* + * Perform a breadth first walk of a dependency graph optionally calling a + * closure for each node in the graph. + * + * @param root Root of the graph to traverse. + * @param closure Closure to call for each node in the graph. + * The closure is called with + * (DependencyResult dependency, List parents) + * where dependency is the current dependency and parents is the list of + * parents of the current dependency. This value can be null to just retrieve + * a flat list of nodes in the graph. + * + * @returns List of (dependency, graph nodes) traversed. + */ +List>> + walkResolvedComponentResultGraph(ResolvedComponentResult root, closure) { + // List of (dependency, parentsList) tuples where dependency is a + // DependencyResult describing a node in the graph and parentsList is the + // list of DependencyResult nodes that are parents of the "dependency" node. + List>> graphRemaining = [] + root.dependencies.each { + graphRemaining.add( + new Tuple2>(it, [])) + } + + List>> summaryGraph = [] + // Map of group:artifact to versioned package specification + // strings. + while (graphRemaining.size > 0) { + // Process current node in the graph. + Tuple2> currentNode = + graphRemaining[0] + DependencyResult dependency = currentNode.first + List parents = currentNode.second + summaryGraph.add(currentNode) + graphRemaining = graphRemaining.drop(1) + + // Call the closure the current node and the current node's parents. + if (closure) closure(dependency, parents) + + if (dependency instanceof ResolvedDependencyResult) { + ResolvedComponentResult componentResult = dependency.selected + List parentsOfChildren = parents.clone() + parentsOfChildren.addAll(0, [dependency]) + graphRemaining.addAll( + 0, componentResult.dependencies.collect { + new Tuple2>( + it, parentsOfChildren) + }) + } + } + return summaryGraph +} + +/* + * Add the most recent package to a map indexed by group:artifact. + * + * @param packages Map of package specification strings by group:artifact. + * @param pkg Package to add to the map if it's the most recent versiom. + * + * @returns Map of package specification strings. + */ +Map addMostRecentPackageToMap( + Map packages, PackageSpecifier pkg) { + PackageSpecifier existing = packages[pkg.groupArtifactString] + if (existing) pkg = PackageSpecifier.sortByVersion([existing, pkg])[-1] + packages[pkg.groupArtifactString] = pkg + return packages +} + +/* + * Walk a dependency graph loosing the version constraints for the parent of + * each conflicting dependency. + * + * This walks the supplied graph and loosens the version constraints using + * loosenVersionExpression() for each parent of a conflicting dependency. + * The method returns a map of package strings that can be used in another + * attempt to resolve without conflicts. + * + * @param root Root of the graph to traverse. + * @param requestedPackages Set of package dependency strings that were used + * to generate the resolved dependency graph. + * @param keepPackages Set of package dependency strings that should not have + * dependency expressions modified. + * + * @returns Tuple of [conflictsFound, packages] where + * "conflictsFound" is a boolean that indicates whether any conflicting + * packages were found in the graph and "packages" is a set of package + * specifiers from the set requestedPackages with potentially loosened + * version constraints. + */ +Tuple2> loosenVersionContraintsForConflicts( + ResolvedComponentResult root, Set requestedPackages) { + // NOTE: The version field is ignored in the returned map, this is only used + // to look up the type specifier for each package. + Map requestedPackagesByGroupArtifact = + PackageSpecifier.mostRecentByGroupArtifact(requestedPackages) + + // Build a set of version locked package set indexes that should not be + // version locked. + Set versionLockedSetBlacklist = createVersionLockedSetBlacklist( + walkResolvedComponentResultGraph(root, null).collect { + DependencyResult dependency, List parents -> + PackageSpecifier.fromModuleComponentSelector( + dependency.requested as ModuleComponentSelector) + }) + + // Map of whether each package is conflicting by group:artifact. + Map conflictingByGroupArtifact = [:] + def trackConflictState = { PackageSpecifier pkg, Boolean isConflicting -> + String groupArtifact = pkg.groupArtifactString + conflictingByGroupArtifact[groupArtifact] = + conflictingByGroupArtifact.get(groupArtifact, isConflicting) | + isConflicting + return isConflicting + } + + // Map of package specification strings indexed by group:artifact. + Map packages = [:] + Map> versionLockedPackages = [:] + + // Closure which adds a dependency to the packages map if it is present in + // the group:artifact set. + def addDependencyToPackages = { PackageSpecifier pkg, Boolean isConflicting -> + // Look up the requested package so that the search continues from the user + // requested packages. + PackageSpecifier requestedPackage = requestedPackagesByGroupArtifact[ + pkg.groupArtifactString] + if (requestedPackage) pkg = requestedPackage + + // If the package is version-locked add it to the tracking set so it can + // be reconciled later. + int versionLockedIndex = addVersionLockedPackageToSet( + pkg, versionLockedPackages, versionLockedSetBlacklist) + if (versionLockedIndex > 0) { + return trackConflictState(pkg, isConflicting) + } + + // If this package is part of a blacklisted version-locked package set but + // the current package version is version-locked we're going to end up with + // a conflict across this set so loosen the version of this package. + if (((-versionLockedIndex) in versionLockedSetBlacklist) && + getVersionLockedPackageIndex(pkg) > 0) { + isConflicting = true + } + + // If the package was specified by the user as an absolute version, + // keep the user specified version. + if (requestedPackage && + requestedPackage.versionRange.matchType == + VersionExpressionMatchType.ABSOLUTE && + requestedPackage.versionRange.minimumVersion.minimumValue == + requestedPackage.versionRange.maximumVersion.maximumValue && + isConflicting) { + if (!(requestedPackage.groupArtifactString in + conflictingByGroupArtifact.keySet())) { + logger.info(sprintf("Keeping conflicting package %s as it's " + + "explicitly specified at version %s", + requestedPackage.specString, + requestedPackage.versionExpression)) + } + addMostRecentPackageToMap(packages, requestedPackage) + return trackConflictState(requestedPackage, false) + } + + // If this package wasn't specified by the user, ignore it. + if (!requestedPackage) { + return trackConflictState(pkg, isConflicting) + } + + // Loosen the version expression for this package if it is currently + // conflicting. + PackageSpecifier newPkg = + new PackageSpecifier( + group: pkg.group, + artifact: pkg.artifact, + classifier: pkg.classifier, + versionExpression: ( + isConflicting ? + pkg.versionRange.matchType == VersionExpressionMatchType.RANGE ? + pkg.versionRange.maximumVersion.loosen().version : + pkg.versionRange.minimumVersion.loosen().version : + pkg.versionRange.expression), + artifactType: pkg.artifactType) + addMostRecentPackageToMap(packages, newPkg) + return trackConflictState(newPkg, isConflicting) + } + + // Walk the resolved graph tracking whether conflicts are present. + walkResolvedComponentResultGraph( + root, { DependencyResult dependency, List parents -> + // Wrap the selector with a PackageSpecifier keeping setting the version + // expression to the selected package version. + PackageSpecifier requestedPackage = + PackageSpecifier.fromModuleComponentSelector( + dependency.requested as ModuleComponentSelector) + ResolvedComponentResult resolved = + (dependency instanceof ResolvedDependencyResult) ? + (dependency as ResolvedDependencyResult).selected : null + boolean conflicting = resolved == null || + resolved.selectionReason.isConflictResolution() + // Gradle has resolved the conflict but it still may not satisfy the + // dependency specification, so check the selected version with the + // requested version of the dependency. + if (resolved && conflicting) { + PackageSpecifier resolvedPackage = + PackageSpecifier.fromModuleVersionIdentifier(resolved.moduleVersion) + if (requestedPackage.versionRange.inRange( + resolvedPackage.versionRange.minimumVersion)) { + conflicting = false + } + } + addDependencyToPackages(requestedPackage, conflicting) + }) + + // Walk the resolved graph again, this time applying any required changes to + // parents or children of conflicting dependencies. + boolean conflictsFound = false + List>> summaryGraph = + walkResolvedComponentResultGraph( + root, { DependencyResult dependency, List parents -> + PackageSpecifier pkg = PackageSpecifier.fromModuleComponentSelector( + dependency.requested as ModuleComponentSelector) + if (conflictingByGroupArtifact[ + pkg.groupArtifactString]) { + conflictsFound = true + parents.each { + addDependencyToPackages( + PackageSpecifier.fromModuleComponentSelector( + it.requested as ModuleComponentSelector), true) + } + } + + // If the package version spec can't be loosened any further and this + // was requested by the caller of this method, add child packages to the + // set of dependencies to loosen in the next iteration. + boolean overrideTransitiveDependencies = + pkg.versionExpression == "+" && + (pkg.groupArtifactString in requestedPackagesByGroupArtifact) + if (overrideTransitiveDependencies && + (dependency instanceof ResolvedDependencyResult)) { + // Find the set of packages, if any, to override. + List> packagesToOverride = + dependency.selected.dependencies.findResults { + PackageSpecifier dependencyPkg = + PackageSpecifier.fromModuleComponentSelector( + it.requested as ModuleComponentSelector) + if (dependencyPkg.groupArtifactString in packages) { + return null + } + return new Tuple2( + dependencyPkg, it.requested as ModuleComponentSelector) + } + // Add packages to set to override on the next resolution attempt. + if (packagesToOverride) { + logger.quiet( + sprintf("Overriding transitive dependencies of %s: %s", + pkg.specString, packagesToOverride.collect { + PackageSpecifier unused, + ModuleComponentSelector requested -> + requested.toString() }.toString())) + packagesToOverride.each { + PackageSpecifier dependencyPkg, ModuleComponentSelector unused -> + addMostRecentPackageToMap(packages, dependencyPkg) + } + } + } + }) + + // Select the most recent version of each version-locked package and add to + // the set. + mostRecentVersionLockedPackagesFromSet(versionLockedPackages).each { + if (it.groupArtifactString in requestedPackagesByGroupArtifact) { + addMostRecentPackageToMap(packages, it) + } + } + + // Collate the set of conflicts after resolution. + // This data structure maps groupArtifact of a conflicting package to a + // list of paths through the dependency tree in the form root/child/leaf to + // each conflict. + Map> conflictingDependencies = [:] + summaryGraph.each { + DependencyResult dependency, List parents -> + if (!(dependency instanceof ResolvedDependencyResult)) return + PackageSpecifier conflictPkg = + PackageSpecifier.fromModuleComponentSelector( + dependency.requested as ModuleComponentSelector) + String groupArtifact = conflictPkg.groupArtifactString + if (!conflictingByGroupArtifact[groupArtifact]) return + + List parentPkgs = parents.collect { + PackageSpecifier.fromModuleComponentSelector( + it.requested as ModuleComponentSelector) + } + String pathToConflict = PackageSpecifier.specStrings( + ([conflictPkg] + parentPkgs).reverse()).join("/") + Set conflictPaths = + conflictingDependencies.get(groupArtifact, [].toSet()) + conflictPaths.add(pathToConflict) + conflictingDependencies[groupArtifact] = conflictPaths + } + + logger.info("=== Updated Dependencies ===") + conflictingDependencies.keySet().sort().each { String conflictGroupArtifact -> + List conflictPaths = + conflictingDependencies[conflictGroupArtifact].sort() + // If only one package is reported as a conflict, it's a parent node in the + // graph of the conflict so ignore it. + if (conflictPaths.size() > 1) { + logger.quiet(sprintf("%s conflicting due to package(s):", + conflictGroupArtifact)) + conflictPaths.each { logger.quiet(sprintf("- %s", it)) } + } + } + // 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", + PackageSpecifier.specStrings( + packages.values()).toString())) + + return new Tuple2>( + conflictsFound, packages.values().toSet()) +} + +/* + * Given two sets of package specifications that specify potentially different + * versions, create a map that describes all versions that have changed. + * Only the packages in the oldPackages set are considered, all packages that + * exist in newPackages that are not in oldPackages are ignored and packages + * that are removed are ignored. + * + * @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. + */ +Map>> getModifiedPackageVersions( + Iterable oldPackages, + 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).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 -> + List newItems = + newPackagesByGroupArtifact.get(groupArtifact, []) + oldItems.each { PackageSpecifier oldPackage -> + // NOTE: Unlike similar languages, groovy's map.get inserts the + // retrieved item into the map. + List> modified = + packagesModified[groupArtifact] + if (!modified) modified = [] + if (newItems) { + newItems.each { PackageSpecifier newPackage -> + VersionRange oldRange = oldPackage.versionRange + VersionRange newRange = newPackage.versionRange + // Only take into account overlapping ranges if the previous range + // wasn't a minimum value without wildcards. For example, + // 1.2.3 typically means that user wanted to select version 1.2.3 + // not 1.2.3 or any subsequent version. + boolean checkInRange = + oldRange && + (oldRange.minimumVersion.hasWildcards || + oldRange.matchType != VersionExpressionMatchType.MINIMUM) + // Determines whether the new range bounds are within the previous + // range. + boolean newRangeInOldRange = + newRange && ( + checkInRange ? + (oldRange.inRange(newRange.minimumVersion) || + oldRange.inRange(newRange.maximumVersion)) : + oldRange.minimumVersion == newRange.minimumVersion) + if (!oldRange || !newRange || !newRangeInOldRange) { + modified.add(new Tuple2( + oldPackage, newPackage)) + } + } + } + if (modified) packagesModified[groupArtifact] = modified + } + } + return packagesModified +} + +/* + * Attempt resolution of the specified packages, loosening version constraints + * if resolution fails due to conflict package versions. + * + * @param packages Packages to resolve the dependencies of. + * @param packageMapper Package mapping to apply on dependency resolution. + * + * @returns Tuple of [packages, configuration] where: + * packages is the set of package specifications that were successfully resolved + * or the original list if it's not possible to find a set of packages that do + * not conflict. + * configuration is the Gradle configuration used to resolve the packages. + */ +Tuple2, Configuration> resolveConflictingPackages( + 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() + int resolutionAttempt = 1 + Configuration userPackagesToQuery = null + boolean resolutionComplete = false + + while (true) { + // 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. + 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. + resolutionResult.allDependencies.findAll { + it instanceof UnresolvedDependencyResult + }.each { + logger.quiet( + sprintf("Resolve failed due to %s missing for %s, attempted " + + "to use %s, failed due to %s", it.requested, it.from, + it.attempted, it.failure)) + } + + Set newPackages = currentPackages + boolean conflictsFound = false + (conflictsFound, newPackages) = loosenVersionContraintsForConflicts( + resolutionResult.root, currentPackages) + boolean packagesChanged = !newPackages.equals(currentPackages) + currentPackages = newPackages + logger.quiet(sprintf("Resolution attempt: %d, conflicts detected: %b, " + + "updated packages: %b", resolutionAttempt, + conflictsFound, packagesChanged)) + resolutionAttempt++ + if (!conflictsFound) { + // Create a new configuration for resolution with the selected packages. + resolutionComplete = true + continue + } else if (!packagesChanged) { + logger.warn(sprintf("Unable to find a set of non-conflicting artifacts " + + "for packages %s.", + PackageSpecifier.specStrings( + currentPackages).toString())) + break + } + } + // Restore any packages that were not present during the conflict resolution + // process. + Set foundPackages = + PackageSpecifier.mostRecentByGroupArtifact(currentPackages).keySet() + currentPackages += packages.findAll { + !(it.groupArtifactString in foundPackages) + }.toSet() + return new Tuple2, Configuration>(currentPackages, + userPackagesToQuery) +} + +/* + * For each missing package search for a srcaar artifact. + * + * JarResolverLib will implicitly search for .srcaar artifacts in a maven + * package in addition to aar and jar artifacts. Since Gradle doesn't know + * what a srcaar is and it's likely each POM doesn't reference the srcaar + * either, we resolve dependencies during the configuration phase to + * determine which packages are missing, add the srcaar artifact specifier + * to search for srcaar files then search again in an attempt to resolve + * 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, PackageMapper packageMapper) { + // Search for all packages, excluding those with explicit artifact types and + // find the missing set. + Map searchPackages = + PackageSpecifier.mostRecentByGroupArtifact(packages).findAll { + !it.value.artifactType + } + + Configuration findMissingConfig = project.configurations.create("findMissing") + packageMapper.applyToProjectConfiguration(findMissingConfig, project) + PackageSpecifier.specStrings(packages).each { + project.dependencies.add(findMissingConfig.name, it) + } + + def artifactsToGroupArtifact = { Iterable artifacts -> + return artifacts.collect { + PackageSpecifier.fromResolvedArtifact(it).groupArtifactString + } + } + + Set foundArtifacts = artifactsToGroupArtifact( + findMissingConfig.resolvedConfiguration. + lenientConfiguration.getArtifacts(Specs.satisfyAll())) + Set missingPackages = searchPackages.keySet().minus(foundArtifacts) + Map fallbackPackages = + missingPackages.collectEntries { + PackageSpecifier pkg = searchPackages[it].clone() + pkg.artifactType = "srcaar" + return [it, pkg] + } + + // 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) + } + foundArtifacts = artifactsToGroupArtifact( + searchForSrcAarConfig.resolvedConfiguration. + lenientConfiguration.getArtifacts(Specs.satisfyAll())) + + // Replace all user supplied packages in the output set with discovered srcaar + // package specs. + Set outputPackages = [].toSet() + packages.each { + String groupArtifact = it.groupArtifactString + if (groupArtifact && groupArtifact in foundArtifacts) { + outputPackages.add(fallbackPackages[groupArtifact]) + } else { + outputPackages.add(it) + } + } + return outputPackages +} + +def testGetComponentsFromPackage() { + [["org.test.psr:something-neat:1.2.3@special", + new PackageSpecifier(group: "org.test.psr", + artifact: "something-neat", + versionExpression: "1.2.3", + artifactType: "special")], + ["org.test.psr:something-neat:1.2.3", + new PackageSpecifier(group: "org.test.psr", + artifact: "something-neat", + versionExpression: "1.2.3", + artifactType: "")], + ["org.test.psr:something-neat", + new PackageSpecifier(group: "org.test.psr", + artifact: "something-neat", + versionExpression: "", + artifactType: "")], + ["org.test.psr", + new PackageSpecifier(group: "org.test.psr", + artifact: "", + versionExpression: "", + artifactType: "")], + ["", new PackageSpecifier()]].each { + String packageSpecifier, PackageSpecifier expected -> + PackageSpecifier result = PackageSpecifier.fromString(packageSpecifier) + if (result != expected) { + throw new Exception( + sprintf("Invalid components %s, expected %s for package '%s'", + result, expected, packageSpecifier)) + } + } +} + +def testPackageSpecifierStrings() { + [[PackageSpecifier.fromString("a-b:c-d:1.2.3@srcaar"), + ["a-b:c-d:1.2.3@srcaar", "a-b:c-d", "a-b.c-d-1.2.3.aar"]], + [PackageSpecifier.fromString("a-b:c-d:1.2.3@aar"), + ["a-b:c-d:1.2.3@aar", "a-b:c-d", "a-b.c-d-1.2.3.aar"]], + [PackageSpecifier.fromString("a-b:c-d:1.2.3"), + ["a-b:c-d:1.2.3", "a-b:c-d", "a-b.c-d-1.2.3"]], + [PackageSpecifier.fromString("a-b:c-d"), + ["a-b:c-d", "a-b:c-d", "a-b.c-d"]], + [PackageSpecifier.fromString("a-b"), ["a-b", "", ""]], + [PackageSpecifier.fromString(""), ["", "", ""]]].each { + pkg, expected -> + List result = [pkg.specString, + pkg.groupArtifactString, + pkg.filename] + if (result != expected) { + throw new Exception( + sprintf("Invalid strings returned %s, expected %s for %s", + result, expected, pkg)) + } + } +} + +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", + "a.b.c:e-f": "a.b.c:e-f:+"]], + [["a.b.c:d-e:3.0.0", "a.b.c:d-e:1.2.+", "a.b.c:d-e:2.1.+"], + ["a.b.c:d-e": "a.b.c:d-e:3.0.0"]]].each { + List packages, Map expected -> + Map result = + PackageSpecifier.mostRecentByGroupArtifact( + PackageSpecifier.fromStrings(packages)).collectEntries { + [it.key, it.value.specString] + } + if (result != expected) { + throw new Exception( + sprintf("Unexpected map %s, expected %s for %s", + result.toString(), expected.toString(), + packages.toString())) + } + } +} + +def testLoosenVersionExpression() { + ["1.2.3": "1.2.3+", + "1.2.3+": "1.2.+", + "1.2.+": "1.2+", + "1.2+": "1.+", + "1.+": "1+", + "1+": "+", + "+": "+"].each { String versionString, String expected -> + String result = (new Version(versionString)).loosen().version + if (result != expected) { + throw new Exception( + sprintf("Invalid loose version expression %s, " + + "expected %s for %s", result, expected, versionString)) + } + } +} + +def testGetVersionLockedPackageIndex() { + ["com.google.android.gms:play-services-base:12.0.1": 1, + "com.google.firebase:firebase-core:12.0.1": 1, + "com.android.support:support-v4:23.0.+": 2, + "com.google.firebase:firebase-app-unity:4.3.0": 0, + "com.google.android.gms:play-services-base:15.1.2": -1].each { + packageSpec, expectedIndex -> + int index = getVersionLockedPackageIndex( + PackageSpecifier.fromString(packageSpec)) + if (index != expectedIndex) { + throw new Exception( + sprintf("Invalid package lock %d, expected %d for %s", + index, expectedIndex, packageSpec)) + } + } +} + +def testAddVersionLockedPackageToSet() { + Map> lockedPackages = [:] + [["com.google.android.gms:play-services-base:12.0.1", true], + ["com.google.firebase:firebase-core:12.0.1", true], + ["com.android.support:support-v4:23.0.+", true], + ["com.android.support:support-vector-drawable:24+", true], + ["com.google.firebase:firebase-app-unity:4.3.0", false], + ["com.google.android.gms:play-services-base:15.1.2", false]].each { + String packageSpecifier, boolean expected -> + int resultIndex = addVersionLockedPackageToSet( + PackageSpecifier.fromString(packageSpecifier), + lockedPackages, [].toSet()) + boolean result = resultIndex > 0 + if (expected != result) { + throw new Exception( + sprintf("%s was incorrectly added to a version-locked set, " + + "returned %b, expected %b", packageSpecifier, result, + expected)) + } + } + Map> expectedLockedPackages = + [1: [PackageSpecifier.fromString( + "com.google.android.gms:play-services-base:12.0.1"), + PackageSpecifier.fromString( + "com.google.firebase:firebase-core:12.0.1")].toSet(), + 2: [PackageSpecifier.fromString( + "com.android.support:support-v4:23.0.+"), + PackageSpecifier.fromString( + "com.android.support:support-vector-drawable:24+")].toSet(), + 0: [PackageSpecifier.fromString( + "com.google.firebase:firebase-app-unity:4.3.0")].toSet(), + (-1): [PackageSpecifier.fromString( + "com.google.android.gms:play-services-base:15.1.2")].toSet()] + if (lockedPackages != expectedLockedPackages) { + throw new Exception( + sprintf("Generated invalid set of locked packages %s, expected %s", + lockedPackages.toString(), expectedLockedPackages.toString())) + } +} + +def testCreateVersionLockedSetBlacklist() { + Set expectedBlacklist = [1].toSet() + Set blacklist = createVersionLockedSetBlacklist( + ["com.google.android.gms:play-services-base:12.0.1", + "com.google.firebase:firebase-core:12.0.1", + "com.google.android.gms:play-services-base:15.+", + "com.android.support:support-v4:23.0.+"].collect { + PackageSpecifier.fromString(it) + }) + if (blacklist != expectedBlacklist) { + throw new Exception( + sprintf("Created invalid blacklist of locked package groups %s, " + + "expected %s", blacklist, expectedBlacklist)) + } +} + +def testAddVersionLockedPackageToSetWithBlacklist() { + Set blacklist = [1].toSet() + Map> lockedPackages = [:] + ["com.google.android.gms:play-services-base:12.0.1": -1, + "com.google.firebase:firebase-core:12.0.1": -1, + "com.android.support:support-v4:23.0.+": 2].each { + String packageSpecifier, int expectedIndex -> + int resultIndex = addVersionLockedPackageToSet( + PackageSpecifier.fromString(packageSpecifier), + lockedPackages, blacklist) + if (resultIndex != expectedIndex) { + throw new Exception( + sprintf("%s was incorrectly added to a version-locked set, " + + "return %d, expected %d", packageSpec, resultIndex, + expectedIndex)) + } + } +} + +def testMostRecentVersionLockedPackagesFromSet() { + Map> versionLockedSets = [ + 1: [PackageSpecifier.fromString( + "com.google.android.gms:play-services-base:12.0.1"), + PackageSpecifier.fromString( + "com.google.firebase:firebase-core:12.0.+")].toSet(), + 2: [PackageSpecifier.fromString( + "com.android.support:support-v4:23.0.+"), + PackageSpecifier.fromString( + "com.android.support:support-vector-drawable:24.+")].toSet()] + Iterable versionLockedList = + PackageSpecifier.specStrings( + mostRecentVersionLockedPackagesFromSet(versionLockedSets)) + Iterable expectedPackageList = [ + "com.android.support:support-v4:24.+", + "com.android.support:support-vector-drawable:24.+", + "com.google.android.gms:play-services-base:12.0.+", + "com.google.firebase:firebase-core:12.0.+", + ] + if (versionLockedList != expectedPackageList) { + throw new Exception( + sprintf("Invalid version-locked packages %s, expected %s " + + "given %s", versionLockedList, expectedPackageList, + versionLockedSets)) + } +} + +def testVersionLockPackages() { + List packages = [ + "com.google.android.gms:play-services-base:12.0.1", + "com.google.android.gms:play-services-core:12.0.+", + "com.android.support:support-v4:23.0.+", + "com.android.support:support-vector-drawable:24.+", + "com.foo.bar:do-not-include-this:1.2.3", + ].collect { PackageSpecifier.fromString(it) } + + Iterable expectedPackageList = [ + "com.android.support:support-v4:24.+", + "com.android.support:support-vector-drawable:24.+", + "com.google.android.gms:play-services-base:12.0.+", + "com.google.android.gms:play-services-core:12.0.+", + ] + + Iterable versionLockedList = + PackageSpecifier.specStrings(versionLockPackages(packages)) + if (versionLockedList != expectedPackageList) { + throw new Exception( + sprintf("Invalid version-locked packages %s, expected %s given %s", + versionLockedList, expectedPackageList, + PackageSpecifier.specStrings(packages))) + } +} + +def testSortPackagesByVersion() { + List unsorted = [ + "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.+", + "a.b.c:e-f:[0.5.2,3.2.0]", + ] + List sortedByVersion = PackageSpecifier.specStrings( + PackageSpecifier.sortByVersion( + unsorted.collect { + PackageSpecifier.fromString(it) + } + )) + if (sortedByVersion != expectedSortedByVersion) { + throw new Exception( + sprintf("Invalid sorted package list %s, expected %s for %s", + sortedByVersion, expectedSortedByVersion, unsorted)) + } +} + +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", + "e.f.g:a-b:[0.1.0]", + "e.f.g:a-b:0.1.0", + "to.be:removed:1.0.0", + "z.z.z:x-y:5.0.0", + "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]", + "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"), + PackageSpecifier.fromString("a.b.c:e-f:1.3.0")]], + "a.b.c:f-h": [[PackageSpecifier.fromString("a.b.c:f-h:4.5.6"), + PackageSpecifier.fromString("a.b.c:f-h:4.6.2")]], + "z.z.z:x-y": [[PackageSpecifier.fromString("z.z.z:x-y:5.0.0"), + 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), + packageMapper) + if (modifiedPackages != expectedModifiedPackages) { + throw new Exception( + sprintf("Invalid modified packages %s, expected %s for %s", + modifiedPackages, expectedModifiedPackages, + [oldPackages, newPackages])) + } +} + +def testVersionObject() { + // Ensure component parsing of an empty version return no components. + Version version; + version = new Version() + if (version.components != [] || version.comparableComponents != []) { + throw new Exception("Components should be empty for an empty Version") + } + + // Parse comparable components from the version string. + List expectedComparableComponents = + ["10", ".", "128", ".", "1", ".", "1234", "-alpha", "1", ".", "3", ".", "5"] + version = new Version("10.128.1.1234-alpha1.3.5") + List components = version.comparableComponents + if (components != expectedComparableComponents) { + throw new Exception( + sprintf("Invalid version comparable components %s, expected %s for %s", + components, expectedComparableComponents, version)) + } + + // Parse components from the version string. + List expectedComponents = + ["10", "128", "1", "1234-alpha1", "3", "5"] + components = version.components + if (components != expectedComponents) { + throw new Exception( + sprintf("Invalid version components %s, expected %s for %s", + components, expectedComponents, version)) + } + + // Get maximum and minimum values. + [[new Version("1.2.+"), [new Version("1.2.0"), + new Version("1.2.2147483647")]], + [new Version("1.2.2+"), [new Version("1.2.2"), + new Version("1.2.2147483644")]], + [new Version("1.+.0"), [new Version("1.0"), new Version("1.2147483647")]] + ].each { + Version testVersion, List expectedMinAndMax -> + List result = [testVersion.minimumValue, + testVersion.maximumValue] + if (result != expectedMinAndMax) { + throw new Exception( + sprintf("Version %s expected min & max %s, returned %s", + testVersion, expectedMinAndMax, result)) + } + } + + // Comparisons test cases in the form [lhs, rhs, result]. + [[new Version("10.128.1.1234-alpha1.3.5"), + new Version("10.128.1.1234-alpha1.3.5"), 0], + [new Version("10.128.1.1234-alpha1.3.5"), + new Version("10.128.1.1234-beta1.3.5"), -1], + [new Version("10.128.1.1234-beta1.3.5"), + new Version("10.128.1.1234-alpha1.3.5"), 1], + [new Version("01.128.1.1234-alpha1"), + new Version("10.128.1.1234-alpha1"), -1], + [new Version("10.128.1.1234-alpha1"), + new Version("01.128.1.1234-alpha1"), 1], + [new Version("1.2.+"), + new Version("1.2.+"), 0], + [new Version("1.2.3+"), + new Version("1.2.3"), 1], + [new Version("1.2.+"), + new Version("1.2.3+"), 1], + [new Version("1.2.3"), + new Version("1.2"), 1], + [new Version("1.2.3"), + new Version("2.2"), -1], + ].each { Version lhs, Version rhs, int expectedResult -> + int compareResult = (lhs <=> rhs) + if (compareResult != expectedResult) { + throw new Exception( + sprintf("Version %s compared with %s returned unexpected result %d " + + "vs. %d", lhs, rhs, compareResult, expectedResult)) + } + } +} + +def testSortVersionStrings() { + List unsorted = [ + "0.1.2-test-za1", + "0.1.2-test-a2a", + "0.1.2-prod-ab", + "0.1.2-prod-12a", + "4.0.+", + "0.1.2b", + "1.2.3", + "0.1.2a", + "4.0.0+", + "1.2", + "3.2", + "5", + "0.1.2", + "4.+", + ] + List expected = [ + "0.1.2", + "0.1.2-prod-12a", + "0.1.2-prod-ab", + "0.1.2-test-a2a", + "0.1.2-test-za1", + "0.1.2a", + "0.1.2b", + "1.2", + "1.2.3", + "3.2", + "4.0.0+", + "4.0.+", + "4.+", + "5", + ] + List result = Version.sort( + unsorted.collect { new Version(it) }).collect { it.version } + if (result != expected) { + throw new Exception( + sprintf("Version sorting failed %s, expected %s for %s", + result, expected, unsorted)) + } +} + +def testVersionRangeMaximumVersionOfRange() { + [[VersionRange.fromExpression("[1.2.3,2.0.0]"), "2.0.0"], + [VersionRange.fromExpression("[ 1.2.3 , 2.0.0 ]"), "2.0.0"], + [VersionRange.fromExpression(" 1.2.3 "), "1.2.3"], + [VersionRange.fromExpression("1.2.3+"), "1.2.2147483643"], + [VersionRange.fromExpression("1.2.+"), "1.2.2147483647"], + [VersionRange.fromExpression("+"), "2147483647"], + [VersionRange.fromExpression(""), "0"]].each { range, expectedString -> + String maxVersionString = range.maximumVersionOfRange.version + if (maxVersionString != expectedString) { + throw new Exception( + sprintf("Unexpected max version for range %s, returned %s, " + + "expected %s", range, maxVersionString, expectedString)) + } + } +} + +def testVersionRangeVersionInRange() { + [[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], + [VersionRange.fromExpression("1.2.3"), new Version("1.2.4"), true], + [VersionRange.fromExpression("1.2.3"), new Version("1.2.2"), false], + [VersionRange.fromExpression("[1.2.3]"), new Version("1.2.3"), true], + [VersionRange.fromExpression("[1.2.3]"), new Version("1.2.4"), false], + [VersionRange.fromExpression("[1.2.3]"), new Version("1.2.2"), false], + [VersionRange.fromExpression("[1.2.1,1.3.0]"), new Version("1.2.1"), true], + [VersionRange.fromExpression("[1.2.1,1.3.0]"), new Version("1.3.0"), true], + [VersionRange.fromExpression("[1.2.1,1.3.0]"), new Version("1.2.0"), false], + [VersionRange.fromExpression("[1.2.1,1.3.0]"), new Version("1.3.1"), false], + ].each { + VersionRange range, Version version, boolean expected -> + boolean result = range.inRange(version) + if (result != expected) { + throw new Exception( + sprintf("Unexpected result %b vs. %b for version %s in range of %s", + result, expected, version.version, range.expression)) + } + } +} + +def testVersionRangeFromExpression() { + [[new VersionRange(versionExpression: "invalid_version[]", + minimum: "0", maximum: "+", + matchType: VersionExpressionMatchType.NONE), + "invalid_version[]"], + [new VersionRange(versionExpression: "[1.2.3]", + minimum: "1.2.3", maximum: "1.2.3", + matchType: VersionExpressionMatchType.ABSOLUTE), + "[1.2.3]"], + [new VersionRange(versionExpression: "1.2.3", + minimum: "1.2.3", maximum: "+", + matchType: VersionExpressionMatchType.MINIMUM), + "1.2.3"], + [new VersionRange(versionExpression: "[1.2.3,1.3.0]", + minimum: "1.2.3", maximum: "1.3.0", + matchType: VersionExpressionMatchType.RANGE), + "[1.2.3,1.3.0]"], + [new VersionRange(versionExpression: "[1.2.3,1.3.0)", + minimum: "1.2.3", maximum: "1.3.0", + matchType: VersionExpressionMatchType.RANGE), + "[1.2.3,1.3.0)"], + [new VersionRange(versionExpression: "(1.2.3,1.3.0]", + minimum: "1.2.3", maximum: "1.3.0", + matchType: VersionExpressionMatchType.RANGE), + "(1.2.3,1.3.0]"], + [new VersionRange(versionExpression: "(1.2.3,1.3.0)", + minimum: "1.2.3", maximum: "1.3.0", + matchType: VersionExpressionMatchType.RANGE), + "(1.2.3,1.3.0)"], + [new VersionRange(versionExpression: "[,1.2.3]", + minimum: "0", maximum: "1.2.3", + matchType: VersionExpressionMatchType.RANGE), + "[,1.2.3]"], + [new VersionRange(versionExpression: "[,1.2.3)", + minimum: "0", maximum: "1.2.3", + matchType: VersionExpressionMatchType.RANGE), + "[,1.2.3)"], + [new VersionRange(versionExpression: "(,1.2.3]", + minimum: "0", maximum: "1.2.3", + matchType: VersionExpressionMatchType.RANGE), + "(,1.2.3]"], + [new VersionRange(versionExpression: "(,1.2.3)", + minimum: "0", maximum: "1.2.3", + matchType: VersionExpressionMatchType.RANGE), + "(,1.2.3)"], + [new VersionRange(versionExpression: "[1.2.+,1.2.5]", + minimum: "1.2.0", maximum: "1.2.5", + matchType: VersionExpressionMatchType.RANGE), + "[1.2.+,1.2.5]"]].each { VersionRange expected, String versionExpression -> + VersionRange range = VersionRange.fromExpression(versionExpression) + if (range != expected) { + throw new Exception( + sprintf("Invalid range (%s) vs. expected (%s) for " + + "expression %s", range, expected, versionExpression)) + } + } +} + +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() { + testVersionObject() + testSortVersionStrings() + testVersionRangeFromExpression() + testVersionRangeMaximumVersionOfRange() + testVersionRangeVersionInRange() + testGetComponentsFromPackage() + testPackageSpecifierStrings() + testPackageSpecifierVersionRange() + testSortPackagesByVersion() + testMostRecentVersionLockedPackagesFromSet() + testVersionLockPackages() + testGetModifiedPackageVersions() + testGetMostRecentPackagesByGroupArtifact() + testLoosenVersionExpression() + testGetVersionLockedPackageIndex() + testAddVersionLockedPackageToSet() + testCreateVersionLockedSetBlacklist() + testAddVersionLockedPackageToSetWithBlacklist() + testPackageMapperEmpty() + testPackageMapperFromJetifierProcessor() +} + +// Configure project properties. +project.ext { + // List of tasks that copy artifacts. + copyTasks = [] + // List of copied file and the associated artifact tuples. + copiedFileArtifacts = [] + // Set of packages to copy, exposed to the copyPackages task. + packagesToCopy = [].toSet() + // Set of packages with modified versions due to conflicting dependencies. + // Maps the group:artifact to a list of modified package tuples. + packagesModified = [:] + + if (project.hasProperty("RUN_TESTS")) { + runTests() + return + } + + // Get the install location of the Android SDK. + String sdkRoot = null + for (prop in [System.getProperty("ANDROID_HOME"), + project.hasProperty("ANDROID_HOME") ? + getProperty("ANDROID_HOME") : null, + System.getenv("ANDROID_HOME")]) { + if (prop) { + sdkRoot = prop + break + } + } + if (sdkRoot) { + logger.quiet("ANDROID_HOME: " + sdkRoot) + } + androidSdkRoot = sdkRoot + + List mavenUris = [] + // Retrieve a list of command line specified maven repo URIs. + if (project.hasProperty("MAVEN_REPOS")) { + project.getProperty("MAVEN_REPOS").tokenize(";").each { + mavenUris.push(new URI(it)) + } + } + + boolean useMavenLocalRepo = true + if (project.hasProperty("USE_MAVEN_LOCAL_REPO")) { + useMavenLocalRepo = project.getProperty("USE_MAVEN_LOCAL_REPO") == "1" + } + boolean useRemoteMavenRepos = true + 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) { + ["extras/android/m2repository", "extras/google/m2repository"].each { + File path = new File(androidSdkRoot, it) + if (path.exists()) mavenUris.push(path.toURI()) + } + } + // Add Google maven repositories. + if (useRemoteMavenRepos) { + mavenUris.push(project.project.repositories.google().url) + } + + // List of URIs to add to the set of maven sources. + mavenRepoUris = mavenUris + + // Add the repositories here so that we can resolve during configuration + // below. + project.repositories { + for (uri in mavenRepoUris) { + maven { + url uri + } + } + if (useMavenLocalRepo) mavenLocal() + if (useRemoteMavenRepos) { + mavenCentral() + } + } + + project.repositories.each { + logger.quiet(sprintf("MAVEN_REPOS: name=%s url=%s", it.name, it.url)) + } + + // Set of packages to copy to the target directory. + if (!project.hasProperty("PACKAGES_TO_COPY")) { + println helpText + logger.error("Project property PACKAGES_TO_COPY must be specified.") + throw new InvalidUserDataException("Missing PACKAGES_TO_COPY") + } + Set userSpecifiedPackages = + PackageSpecifier.fromStrings( + project.getProperty("PACKAGES_TO_COPY").tokenize(";")) + PackageSpecifier.specStrings(userSpecifiedPackages).each { + logger.quiet("PACKAGES_TO_COPY: " + it) + } + + // Location to copy referenced packages. + if (!project.hasProperty("TARGET_DIR")) { + println helpText + logger.error("Project property TARGET_DIR must be specified.") + throw new InvalidUserDataException("Missing TARGET_DIR") + } + targetDir = project.getProperty("TARGET_DIR") + if (targetDir && !(new File(targetDir)).isAbsolute()) { + targetDir = (new File(System.getProperty("user.dir"), + targetDir)).absolutePath + } + logger.quiet(sprintf("TARGET_DIR: %s", targetDir)) + + // 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, + packageMapper) + + // Resolve while searching for a set of non-conflicting package + // specifications. + Set resolvedConflictingPackages + Configuration resolveConflictConfig + (resolvedConflictingPackages, resolveConflictConfig) = + resolveConflictingPackages(userSpecifiedPackages, packageMapper) + Map resolvedConflictingPackagesByArtifactGroup = + PackageSpecifier.mostRecentByGroupArtifact(resolvedConflictingPackages) + + // Resolve the current set of packages to search for transitive dependencies + // that should be version locked. + Map resolvedPackages = + resolvedConflictingPackagesByArtifactGroup + + PackageSpecifier.mostRecentByGroupArtifact( + resolveConflictConfig.resolvedConfiguration.lenientConfiguration. + getArtifacts(Specs.satisfyAll()).collect { + PackageSpecifier.fromResolvedArtifact(it) + }) + + // If we were unable to find a set of packages with versions that do not + // conflict we may still end up with transitive references to artifacts at + // different versions which should all point at the same version. So, + // if possible, iterate through all fetched artifacts and explicitly pin all + // version-locked transitive dependencies to the same version. + Map versionLockedPackages = + PackageSpecifier.mostRecentByGroupArtifact( + versionLockPackages(resolvedPackages.values())) + logger.quiet(sprintf("version locked packages: %s", + PackageSpecifier.specStrings( + versionLockedPackages.values()).toString())) + + // Build the final set of selected packages. + Map finalSelectedPackages = + resolvedConflictingPackagesByArtifactGroup.findAll { + !(it.key in versionLockedPackages.keySet()) + } + logger.quiet(sprintf("non-version locked packages: %s", + PackageSpecifier.specStrings( + finalSelectedPackages.values()).toString())) + finalSelectedPackages += versionLockedPackages + + // Determine which, if any, package specs were modified. + packagesModified = getModifiedPackageVersions(userSpecifiedPackages, + finalSelectedPackages.values(), + packageMapper) + + // Gather transitive dependencies of all selected packages. + 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 { + ResolvedArtifact artifact -> + PackageSpecifier pkg = PackageSpecifier.fromResolvedArtifact(artifact) + File artifactTargetFile = new File(targetDir, pkg.filename) + if (artifactTargetFile.exists()) { + // If the target file already exists simply report it as copied. + copiedFileArtifacts.add( + new Tuple2(artifactTargetFile, pkg)) + return null + } + return pkg + }.toSet()) + + // Packages that are not found will not be added via the above config so add + // them here. + packagesToCopy = ( + finalSelectedPackages + + PackageSpecifier.mostRecentByGroupArtifact(packagesToCopy)).values().toSet() + + // Add all selected packages and their transitive dependencies to the final + // copy configuration. + PackageSpecifier.specStrings(packagesToCopy).each { + // Add final set of packages to copy to the copyPackagesConfig + // configuration. + 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) + 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)) + } +} + +// Task which depends upon all copy package tasks and summarizes the set of +// copied files when it's complete, displaying: +// * The set of files copied into the target directory. +// * The dependency expression for each artifact that was not found. +// * Any packages that were modified from the dependency expression specified +// by the user. +task copyPackages(dependsOn: project.ext.copyTasks) { + doLast { + List> copiedFileArtifacts = + project.ext.copiedFileArtifacts + if (copiedFileArtifacts) { + println "Copied artifacts:" + copiedFileArtifacts.collect { it.first.name }.toSorted().each { + println it + } + println "" + } + + Set resolvedGroupArtifacts = copiedFileArtifacts.collect { + it.second.groupArtifactString + } + List missingPackages = project.ext.packagesToCopy.findAll { + !(it.groupArtifactString in resolvedGroupArtifacts) + }.collect { + it.specString + } + if (missingPackages) { + println "Missing artifacts:" + missingPackages.toSorted().each { println it } + println "" + } + Map>> packagesModified = + project.ext.packagesModified + if (packagesModified) { + println "Modified artifacts:" + List> modified = [] + packagesModified.values().each { modified.addAll(it) } + modified.sort({ lhs, rhs -> lhs.first <=> rhs.first }) + modified.each { + PackageSpecifier oldVersion, PackageSpecifier newVersion -> + println sprintf("%s --> %s", oldVersion.specString, + newVersion.specString) + } + println "" + } + } +} + +project.defaultTasks = ["copyPackages"] diff --git a/source/AndroidResolver/scripts/download_artifacts_test.gradle b/source/AndroidResolver/scripts/download_artifacts_test.gradle new file mode 100644 index 00000000..02d8cd54 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test.gradle @@ -0,0 +1,936 @@ +/* + * Copyright 2018 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. + */ + +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 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. + * + * @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. + * + * @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. + * + * @param taskToLog Task to remove this logger from. + */ + void uninstall(Task taskToLog) { + taskToLog.logging.removeStandardOutputListener(this) + taskToLog.logging.removeStandardErrorListener(this) + } +} + +project.ext { + // Directory which contains this script. + def scriptDirectory = buildscript.sourceFile.getParentFile() + + // Directory used to execute gradle subprocesses. + outputDir = new File(System.getProperty("user.dir"), + "download_artifacts_test_output") + + // Gradle project under test. + srcSettingsFile = new File(scriptDirectory, "settings.gradle") + srcBuildFile = new File(scriptDirectory, "download_artifacts.gradle") + buildFile = new File(outputDir, srcBuildFile.name) + + // Local test maven repo. + mavenRepo = new File(scriptDirectory, + "download_artifacts_test_assets/m2repository") + + // Fake Android SDK home directory. + androidHome = new File(scriptDirectory, ".") // TODO(smiles): Use a real maven repo here. + + // All test case tasks (createTestTask() appends to this list) + testTaskNames = [] + + // Header in the download script's output that describes the set of artifacts + // that have been copied. + copiedArtifactsHeader = "Copied artifacts:" + // Header in the download script's output that describes the set of artifacts + // that are missing. + missingArtifactsHeader = "Missing artifacts:" + // Header in the download script's output that describes the set of artifacts + // that were modified from what the user requested. + modifiedArtifactsHeader = "Modified artifacts:" +} + +/* + * 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 { + outputs.dir directoryFile + doLast { directoryFile.mkdirs() } + } + return createDirectory +} + +task copyTestScript( + type: Copy, + dependsOn: createDirectoryTask(project.ext.outputDir)) { + description "Copy the test script into the test project" + from srcBuildFile, srcSettingsFile + into outputDir +} + +// Create GradleBuild task StartParameter to execute download_artifacts.build. +StartParameter createStartParameters(String packagesToCopy, File targetDir, + boolean useJetifier) { + def startParameters = new StartParameter() + startParameters.projectProperties = [ + "ANDROID_HOME": project.ext.androidHome, + "PACKAGES_TO_COPY": packagesToCopy, + "TARGET_DIR": targetDir.absolutePath, + "MAVEN_REPOS": mavenRepo.toURI().toString(), + // Only search the test repo for artifacts to make the tests deterministic. + // This disables searching the local Gradle configured repo and remote + // 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. + * + * @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) + byte[] md5sum = digest.digest() + BigInteger bigInt = new BigInteger(1, md5sum) + return bigInt.toString(16).padLeft(32, '0') +} + +/* + * 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) { + if (isZipFile(inputFile) && isZipFile(outputFile)) { + mismatchingFiles += validateZipFilesMatch(inputFile, outputFile) + } else { + mismatchingFiles.add(sprintf("%s (%s) != %s (%s)", + inputFile.path, inputFileChecksum, + outputFile.path, outputFileChecksum)) + } + } + } + } + if (mismatchingFiles) { + throw new TaskExecutionException( + taskToValidate, new Exception( + sprintf("%s failed, unexpected output file(s)\n%s\n\n%s\n", + taskToValidate.name, mismatchingFiles.join("\n"), + taskToValidate.ext.standardOutputErrorLogger.output))) + } +} + +/* + * 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) } + } + if (missingFiles) { + throw new TaskExecutionException( + taskToValidate, new Exception( + sprintf("%s failed, missing expected file(s)\n%s\n\n%s\n", + taskToValidate.name, missingFiles.join("\n"), + taskToValidate.ext.standardOutputErrorLogger.output))) + } +} + +/* + * 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() + List sections = [] + ListcurrentSection = [] + // Adds the current section to the list of sections and flushes the list. + def endSectionClosure = { + if (currentSection) { + sections.add(currentSection.join("\n")) + currentSection = [] + } + } + // Parse sections from the output string. + output.eachLine { + if (validSections.contains(it.trim())) { + currentSection.add(it) + } else if (it == "") { + endSectionClosure() + } else if (currentSection) { + currentSection.add(it) + } + } + endSectionClosure() + 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). + * + * @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. + Map movedOutputInputFileMap = [:] + outputInputFileMap.each { + outputFile, inputFile -> + movedOutputInputFileMap[new File(targetDirFile, outputFile)] = + inputFile != null ? new File(project.ext.mavenRepo, inputFile) : null + } + Task createDirectoryTask = createDirectoryTask(targetDirFile) + createDirectoryTask.dependsOn copyTestScript + iterations.times { + 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, useJetifier) + buildFile project.ext.buildFile + dir project.ext.outputDir + doFirst { ext.standardOutputErrorLogger.install(it) } + doLast { + ext.standardOutputErrorLogger.uninstall(it) + validateOutputFilesExist(it) + validateFilesMatch(it, movedOutputInputFileMap) + if (expectedScriptOutput != null) { + List parsedOutput = downloadScriptOutputToSectionsList( + ext.standardOutputErrorLogger.output) + assert parsedOutput == expectedScriptOutput + } + } + } + project.ext.testTaskNames.add(currentTaskName) + } +} + +createTestTask( + "testDownloadAvailable", + "Downloads a single artifact and it's dependencies from maven.", + "android.arch.core:common:1.0.0", + ["android.arch.core.common-1.0.0.jar": + "android/arch/core/common/1.0.0/common-1.0.0.jar", + "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" + + "android.arch.core.common-1.0.0.jar\n" + + "com.android.support.support-annotations-26.1.0.jar"]) + +createTestTask( + "testDownloadAvailableWithCustomPackaging", + "Downloads a single artifact with custom packaging.", + "com.android.support:support-annotations:23.0.1@magic", + ["com.android.support.support-annotations-23.0.1.magic": + "com/android/support/support-annotations/23.0.1/" + + "support-annotations-23.0.1.magic"], + ["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.", + "android.arch.core:common:1.0.0", + ["android.arch.core.common-1.0.0.jar": + "android/arch/core/common/1.0.0/common-1.0.0.jar", + "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" + + "android.arch.core.common-1.0.0.jar\n" + + "com.android.support.support-annotations-26.1.0.jar"], + 2 /* iterations */) + +createTestTask( + "testDownloadAvailableWithSameName", + "Downloads artifacts with the same artifact name and their dependencies " + + "from a maven repo.", + "android.arch.core:common:1.0.0;android.arch.lifecycle:common:1.0.0;", + ["android.arch.core.common-1.0.0.jar": + "android/arch/core/common/1.0.0/common-1.0.0.jar", + "android.arch.lifecycle.common-1.0.0.jar": + "android/arch/lifecycle/common/1.0.0/common-1.0.0.jar", + "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" + + "android.arch.core.common-1.0.0.jar\n" + + "android.arch.lifecycle.common-1.0.0.jar\n" + + "com.android.support.support-annotations-26.1.0.jar"]) + +createTestTask( + "testDownloadUnavailable", + "Attempts to download a non-existant artifact.", + "apackage.thatdoes:notexist:9.9.9", + [:], + ["Missing artifacts:\n" + + "apackage.thatdoes:notexist:+", + "Modified artifacts:\n" + + "apackage.thatdoes:notexist:9.9.9 --> apackage.thatdoes:notexist:+"]) + +createTestTask( + "testDownloadConflictingVersions", + "Downloads conflicting versions of an artifact with the download script " + + "resolving the conflict.", + "com.android.support:appcompat-v7:23.0.0;" + + "com.android.support:support-v4:24.0.0;", + ["com.android.support.animated-vector-drawable-24.0.0.aar": + "com/android/support/animated-vector-drawable/24.0.0/" + + "animated-vector-drawable-24.0.0.aar", + "com.android.support.appcompat-v7-24.0.0.aar": + "com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.aar", + "com.android.support.support-annotations-24.0.0.jar": + "com/android/support/support-annotations/24.0.0/" + + "support-annotations-24.0.0.jar", + "com.android.support.support-v4-24.0.0.aar": + "com/android/support/support-v4/24.0.0/support-v4-24.0.0.aar", + "com.android.support.support-vector-drawable-24.0.0.aar": + "com/android/support/support-vector-drawable/24.0.0/" + + "support-vector-drawable-24.0.0.aar"], + ["Copied artifacts:\n" + + "com.android.support.animated-vector-drawable-24.0.0.aar\n" + + "com.android.support.appcompat-v7-24.0.0.aar\n" + + "com.android.support.support-annotations-24.0.0.jar\n" + + "com.android.support.support-v4-24.0.0.aar\n" + + "com.android.support.support-vector-drawable-24.0.0.aar", + "Modified artifacts:\n" + + "com.android.support:appcompat-v7:23.0.0 --> " + + "com.android.support:appcompat-v7:24.0.0@aar"]) + +createTestTask( + "testDownloadSrcAar", + "Download a srcaar artifact and validate it's found and " + + "renamed to an aar in the target directory.", + "com.google.firebase:firebase-app-unity:4.3.0;", + ["com.google.firebase.firebase-app-unity-4.3.0.aar": + "com/google/firebase/firebase-app-unity/4.3.0/" + + "firebase-app-unity-4.3.0.srcaar"], + ["Copied artifacts:\n" + + "com.google.firebase.firebase-app-unity-4.3.0.aar"]) + +createTestTask( + "testFirebaseUnityNotVersionLocked", + "Ensure firebase-.*-unity packages are not locked to the same version as " + + "Google Play services or Firebase packages.", + "com.google.firebase:firebase-app-unity:4.3.0;" + + "com.google.android.gms:play-services-basement:9.8.0;", + ["com.google.firebase.firebase-app-unity-4.3.0.aar": + "com/google/firebase/firebase-app-unity/4.3.0/" + + "firebase-app-unity-4.3.0.srcaar", + "com.google.android.gms.play-services-basement-9.8.0.aar": + null /* Downloaded from Google Maven */], + ["Copied artifacts:\n" + + "com.android.support.support-annotations-24.0.0.jar\n" + + "com.android.support.support-v4-24.0.0.aar\n" + + "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.", + "com.android.support:appcompat-v7:23.0.+", + ["com.android.support.appcompat-v7-23.0.1.aar": + "com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.aar", + "com.android.support.support-annotations-23.0.1.jar": + "com/android/support/support-annotations/23.0.1/" + + "support-annotations-23.0.1.jar", + "com.android.support.support-v4-23.0.1.aar": + "com/android/support/support-v4/23.0.1/support-v4-23.0.1.aar"], + ["Copied artifacts:\n" + + "com.android.support.appcompat-v7-23.0.1.aar\n" + + "com.android.support.support-annotations-23.0.1.jar\n" + + "com.android.support.support-v4-23.0.1.aar"]) + +createTestTask( + "testDownloadUsingVersionRange", + "Download an artifact using a version range.", + "com.android.support:support-annotations:[23,26.1.0]", + ["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"]) + +createTestTask( + "testDownloadSnapshotVersion", + "Download a snapshot version of an artifact.", + "com.android.support:support-v4:23.0.1;" + + "com.android.support:support-annotations:27.0.2-SNAPSHOT", + ["com.android.support.support-annotations-27.0.2-SNAPSHOT.jar": + "com/android/support/support-annotations/27.0.2-SNAPSHOT/" + + "support-annotations-27.0.2-SNAPSHOT.jar"], + ["Copied artifacts:\n" + + "com.android.support.support-annotations-27.0.2-SNAPSHOT.jar", + "Missing artifacts:\n" + + "com.android.support:support-v4:27.0.2-SNAPSHOT", + "Modified artifacts:\n" + + "com.android.support:support-v4:23.0.1 --> " + + "com.android.support:support-v4:27.0.2-SNAPSHOT"]) + +createTestTask( + "testDownloadCompatibleCommonDependency", + "Download artifacts with compatible dependencies.", + "org.test.psr:pull:1.0.2;org.test.psr:push:1.0.3", + ["org.test.psr.common-impl-1.0.0.aar": + "org/test/psr/common-impl/1.0.0/common-impl-1.0.0.aar", + "org.test.psr.common-1.0.1.aar": + "org/test/psr/common/1.0.1/common-1.0.1.aar", + "org.test.psr.pull-1.0.2.aar": + "org/test/psr/pull/1.0.2/pull-1.0.2.aar", + "org.test.psr.push-1.0.3.aar": + "org/test/psr/push/1.0.3/push-1.0.3.aar"], + ["Copied artifacts:\n" + + "org.test.psr.common-1.0.1.aar\n" + + "org.test.psr.common-impl-1.0.0.aar\n" + + "org.test.psr.pull-1.0.2.aar\n" + + "org.test.psr.push-1.0.3.aar"]) + +createTestTask( + "testDownloadIncompatibleCommonDependency", + "Download artifacts with incomaptible dependencies.\n" + + "This should result in a forced upgrade of the minimal set of new versions", + "org.test.psr:push:2.0.2;org.test.psr:pull:2.0.3", + ["org.test.psr.pull-2.0.3.aar": "org/test/psr/pull/2.0.3/pull-2.0.3.aar", + "org.test.psr.push-2.0.4.aar": "org/test/psr/push/2.0.4/push-2.0.4.aar", + "org.test.psr.common-impl-2.3.0.aar": + "org/test/psr/common-impl/2.3.0/common-impl-2.3.0.aar", + "org.test.psr.common-2.4.0.aar": + "org/test/psr/common/2.4.0/common-2.4.0.aar"], + ["Copied artifacts:\n" + + "org.test.psr.common-2.4.0.aar\n" + + "org.test.psr.common-impl-2.3.0.aar\n" + + "org.test.psr.pull-2.0.3.aar\n" + + "org.test.psr.push-2.0.4.aar", + "Modified artifacts:\n" + + "org.test.psr:pull:2.0.3 --> org.test.psr:pull:2.0.+\n" + + "org.test.psr:push:2.0.2 --> org.test.psr:push:2.0.+"]) + +createTestTask( + "testDownloadLatestOfSet", + "Download the latest version of a set of dependencies.", + "org.test.psr:common:[3.0.2];org.test.psr:common:[3.0.3]", + ["org.test.psr.common-3.0.3.aar": + "org/test/psr/common/3.0.3/common-3.0.3.aar", + "org.test.psr.common-impl-3.0.2.aar": + "org/test/psr/common-impl/3.0.2/common-impl-3.0.2.aar"], + ["Copied artifacts:\n" + + "org.test.psr.common-3.0.3.aar\n" + + "org.test.psr.common-impl-3.0.2.aar", + "Modified artifacts:\n" + + "org.test.psr:common:[3.0.2] --> org.test.psr:common:[3.0.3]"]) + +createTestTask( + "testDownloadTransitiveConflicts", + "Download artifacts with a conflicting transitive dependency " + + "(common-impl which requires either 2.4.+ via push:2.0.4 or 4.+ via " + + "pull:6.0.1) pinned to a specific version [4.0.0].\n", + "org.test.psr:push:2.0.4;org.test.psr:pull:6.0.1;" + + "org.test.psr:common-impl:[4.0.0]", + ["org.test.psr.common-2.4.0.aar": + "org/test/psr/common/2.4.0/common-2.4.0.aar", + "org.test.psr.common-impl-4.0.0.aar": + "org/test/psr/common-impl/4.0.0/common-impl-4.0.0.aar", + "org.test.psr.pull-6.0.1.aar": + "org/test/psr/pull/6.0.1/pull-6.0.1.aar", + "org.test.psr.push-2.0.4.aar": + "org/test/psr/push/2.0.4/push-2.0.4.aar"], + ["Copied artifacts:\n" + + "org.test.psr.common-2.4.0.aar\n" + + "org.test.psr.common-impl-4.0.0.aar\n" + + "org.test.psr.pull-6.0.1.aar\n" + + "org.test.psr.push-2.0.4.aar"]) + +createTestTask( + "testDownloadIncompatibleMajorVersions", + "Download artifacts with incompatible major versioned dependencies.\n" + + "This should result in a forced upgrade to the latest compatible major " + + "version", + "org.test.psr:push:5.0.1;org.test.psr:pull:6.0.1", + ["org.test.psr.pull-6.0.1.aar": "org/test/psr/pull/6.0.1/pull-6.0.1.aar", + "org.test.psr.push-5.0.1.aar": "org/test/psr/push/5.0.1/push-5.0.1.aar", + "org.test.psr.common-5.0.1.aar": + "org/test/psr/common/5.0.1/common-5.0.1.aar", + "org.test.psr.common-impl-5.0.0.aar": + "org/test/psr/common-impl/5.0.0/common-impl-5.0.0.aar"], + ["Copied artifacts:\n" + + "org.test.psr.common-5.0.1.aar\n" + + "org.test.psr.common-impl-5.0.0.aar\n" + + "org.test.psr.pull-6.0.1.aar\n" + + "org.test.psr.push-5.0.1.aar", + "Modified artifacts:\n" + + "org.test.psr:pull:6.0.1 --> org.test.psr:pull:+\n" + + "org.test.psr:push:5.0.1 --> org.test.psr:push:+"]) + +createTestTask( + "testDownloadOrderingLowestToHighestVersion", + "Download the highest version of the specified set of packages. The\n" + + "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"]) + +createTestTask( + "testDownloadOrderingHighestToLowestVersion", + "Download the highest version of the specified set of packages. The\n" + + "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"]) + +createTestTask( + "testVersionLockedObsoleteTransitiveDependency", + "Packages are upgraded to the most recent version locked package " + + "where a transitive dependency of an initially selected package is removed.", + "org.test.psr.locked:input:1.2.3;" + + "org.test.psr.locked:output:1.5.0;", + ["org.test.psr.locked.input-1.5.0.aar": + "org/test/psr/locked/input/1.5.0/input-1.5.0.aar", + "org.test.psr.locked.output-1.5.0.aar": + "org/test/psr/locked/output/1.5.0/output-1.5.0.aar", + "org.test.psr.locked.new-common-1.5.0.aar": + "org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.aar"], + ["Copied artifacts:\n" + + "org.test.psr.locked.input-1.5.0.aar\n" + + "org.test.psr.locked.new-common-1.5.0.aar\n" + + "org.test.psr.locked.output-1.5.0.aar", + "Modified artifacts:\n" + + "org.test.psr.locked:input:1.2.3 --> org.test.psr.locked:input:1.5.0@aar"]) + +createTestTask( + "testVersionLockedAndNonVersionLocked", + "Packages are upgraded to the most recent non-version locked package " + + "if a mix of version locked vs. non-version locked packages are specified.", + "com.google.android.gms:play-services-basement:12.0.0;" + + "com.google.android.gms:play-services-tasks:12.0.0;" + + "com.google.android.gms:play-services-basement:15.0.0;" + + "a.non:existent-package:1.2.3;", + ["android.arch.core.common-1.0.0.jar": + "android/arch/core/common/1.0.0/common-1.0.0.jar", + "android.arch.lifecycle.common-1.0.0.jar": + "android/arch/lifecycle/common/1.0.0/common-1.0.0.jar", + "android.arch.lifecycle.runtime-1.0.0.aar": + "android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.aar", + "com.android.support.support-annotations-26.1.0.jar": + "com/android/support/support-annotations/26.1.0/" + + "support-annotations-26.1.0.jar", + "com.android.support.support-compat-26.1.0.aar": + "com/android/support/support-compat/26.1.0/support-compat-26.1.0.aar", + "com.android.support.support-core-ui-26.1.0.aar": + "com/android/support/support-core-ui/26.1.0/support-core-ui-26.1.0.aar", + "com.android.support.support-core-utils-26.1.0.aar": + "com/android/support/support-core-utils/26.1.0/" + + "support-core-utils-26.1.0.aar", + "com.android.support.support-fragment-26.1.0.aar": + "com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.aar", + "com.android.support.support-media-compat-26.1.0.aar": + "com/android/support/support-media-compat/26.1.0/" + + "support-media-compat-26.1.0.aar", + "com.android.support.support-v4-26.1.0.aar": + "com/android/support/support-v4/26.1.0/support-v4-26.1.0.aar", + "com.google.android.gms.play-services-basement-15.0.0.aar": + "com/google/android/gms/play-services-basement/15.0.0/" + + "play-services-basement-15.0.0.aar", + "com.google.android.gms.play-services-tasks-15.0.0.aar": + "com/google/android/gms/play-services-tasks/15.0.0/" + + "play-services-tasks-15.0.0.aar", + ], + ["Copied artifacts:\n" + + "android.arch.core.common-1.0.0.jar\n" + + "android.arch.lifecycle.common-1.0.0.jar\n" + + "android.arch.lifecycle.runtime-1.0.0.aar\n" + + "com.android.support.support-annotations-26.1.0.jar\n" + + "com.android.support.support-compat-26.1.0.aar\n" + + "com.android.support.support-core-ui-26.1.0.aar\n" + + "com.android.support.support-core-utils-26.1.0.aar\n" + + "com.android.support.support-fragment-26.1.0.aar\n" + + "com.android.support.support-media-compat-26.1.0.aar\n" + + "com.android.support.support-v4-26.1.0.aar\n" + + "com.google.android.gms.play-services-basement-15.0.0.aar\n" + + "com.google.android.gms.play-services-tasks-15.0.0.aar", + "Missing artifacts:\n" + + "a.non:existent-package:+", + "Modified artifacts:\n" + + "a.non:existent-package:1.2.3 --> a.non:existent-package:+\n" + + "com.google.android.gms:play-services-basement:12.0.0 --> " + + "com.google.android.gms:play-services-basement:15.0.0\n" + + "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"] + startParameters.logLevel = logging.level + project.ext.testTaskNames.add(new String("${name}")) + + description "Run unit tests." + startParameter startParameters + buildFile project.ext.buildFile + dir project.ext.outputDir +} + +// 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/AndroidResolver/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 new file mode 100644 index 00000000..f267458c Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.jar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..54f00f97 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/core/common/1.0.0/common-1.0.0.pom @@ -0,0 +1,48 @@ + + + 4.0.0 + android.arch.core + common + 1.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 + + + + com.android.support + support-annotations + 26.1.0 + compile + + + junit + junit + 4.12 + test + + + org.mockito + mockito-core + 2.7.6 + test + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..3f7a7d78 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.jar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..379a8824 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/common/1.0.0/common-1.0.0.pom @@ -0,0 +1,48 @@ + + + 4.0.0 + android.arch.lifecycle + common + 1.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 + + + + com.android.support + support-annotations + 26.1.0 + compile + + + junit + junit + 4.12 + test + + + org.mockito + mockito-core + 2.7.6 + test + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..ceb329ff Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..adda3801 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/android/arch/lifecycle/runtime/1.0.0/runtime-1.0.0.pom @@ -0,0 +1,49 @@ + + + 4.0.0 + android.arch.lifecycle + runtime + 1.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 + + + + android.arch.lifecycle + common + 1.0.0 + compile + + + android.arch.core + common + 1.0.0 + compile + + + com.android.support + support-annotations + 26.1.0 + compile + + + 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/AndroidResolver/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 new file mode 100644 index 00000000..475a9196 Binary files /dev/null and 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 differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..0a172765 --- /dev/null +++ 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 @@ -0,0 +1,38 @@ + + + 4.0.0 + com.android.support + animated-vector-drawable + 24.0.0 + aar + Android Support AnimatedVectorDrawable + Android Support AnimatedVectorDrawable + 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 + + + + com.android.support + support-vector-drawable + 24.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..af7bec42 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..e1e1004e --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.0/appcompat-v7-23.0.0.pom @@ -0,0 +1,38 @@ + + + 4.0.0 + com.android.support + appcompat-v7 + 23.0.0 + aar + Android AppCompat Library v7 + 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 4 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 + + + + com.android.support + support-v4 + 23.0.0 + aar + compile + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..65f7a4e5 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..3173273d --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/23.0.1/appcompat-v7-23.0.1.pom @@ -0,0 +1,38 @@ + + + 4.0.0 + com.android.support + appcompat-v7 + 23.0.1 + aar + Android AppCompat Library v7 + 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 4 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 + + + + com.android.support + support-v4 + 23.0.1 + aar + compile + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..26da25f7 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..c61888b0 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/appcompat-v7/24.0.0/appcompat-v7-24.0.0.pom @@ -0,0 +1,52 @@ + + + 4.0.0 + com.android.support + appcompat-v7 + 24.0.0 + aar + Android AppCompat Library v7 + 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 4 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 + + + + com.android.support + support-v4 + 24.0.0 + aar + compile + + + com.android.support + support-vector-drawable + 24.0.0 + aar + compile + + + com.android.support + animated-vector-drawable + 24.0.0 + aar + compile + + + 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/AndroidResolver/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 new file mode 100644 index 00000000..1403a20c Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.jar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..1403a20c Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.magic differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..e6ead721 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/23.0.1/support-annotations-23.0.1.pom @@ -0,0 +1,28 @@ + + + 4.0.0 + com.android.support + support-annotations + 23.0.1 + 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/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 new file mode 100644 index 00000000..ec3b6711 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.jar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..f10d5417 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/24.0.0/support-annotations-24.0.0.pom @@ -0,0 +1,28 @@ + + + 4.0.0 + com.android.support + support-annotations + 24.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/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 new file mode 100644 index 00000000..0821412a Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.jar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..99e14791 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-annotations/26.1.0/support-annotations-26.1.0.pom @@ -0,0 +1,28 @@ + + + 4.0.0 + com.android.support + support-annotations + 26.1.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/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 new file mode 100644 index 00000000..24df50ad Binary files /dev/null and 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 differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..3d97cd30 --- /dev/null +++ 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 @@ -0,0 +1,28 @@ + + + 4.0.0 + com.android.support + support-annotations + 27.0.2-SNAPSHOT + 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/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 new file mode 100644 index 00000000..bd37e17a Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..353e030f --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-compat/26.1.0/support-compat-26.1.0.pom @@ -0,0 +1,49 @@ + + + 4.0.0 + com.android.support + support-compat + 26.1.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 + + + + com.android.support + support-annotations + 26.1.0 + compile + + + android.arch.lifecycle + runtime + 1.0.0 + compile + + + support-annotations + * + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..34bfd64e Binary files /dev/null and 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 differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..04d5ba3b --- /dev/null +++ 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 @@ -0,0 +1,43 @@ + + + 4.0.0 + com.android.support + support-core-ui + 26.1.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 + + + + com.android.support + support-annotations + 26.1.0 + compile + + + com.android.support + support-compat + 26.1.0 + compile + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..7187884d Binary files /dev/null and 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 differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..fe9391d2 --- /dev/null +++ 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 @@ -0,0 +1,43 @@ + + + 4.0.0 + com.android.support + support-core-utils + 26.1.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 + + + + com.android.support + support-annotations + 26.1.0 + compile + + + com.android.support + support-compat + 26.1.0 + compile + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..3a8d9c66 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..b55470ad --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-fragment/26.1.0/support-fragment-26.1.0.pom @@ -0,0 +1,49 @@ + + + 4.0.0 + com.android.support + support-fragment + 26.1.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 + + + + com.android.support + support-compat + 26.1.0 + compile + + + com.android.support + support-core-ui + 26.1.0 + compile + + + com.android.support + support-core-utils + 26.1.0 + compile + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..352233e5 Binary files /dev/null and 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 differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..09361a79 --- /dev/null +++ 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 @@ -0,0 +1,43 @@ + + + 4.0.0 + com.android.support + support-media-compat + 26.1.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 + + + + com.android.support + support-annotations + 26.1.0 + compile + + + com.android.support + support-compat + 26.1.0 + compile + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..2f868175 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..162ceb7c --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/23.0.1/support-v4-23.0.1.pom @@ -0,0 +1,37 @@ + + + 4.0.0 + com.android.support + support-v4 + 23.0.1 + 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 4 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 + + + + com.android.support + support-annotations + 23.0.1 + compile + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..4857df0c Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..310ccb47 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/24.0.0/support-v4-24.0.0.pom @@ -0,0 +1,37 @@ + + + 4.0.0 + com.android.support + support-v4 + 24.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 4 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 + + + + com.android.support + support-annotations + 24.0.0 + compile + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..4c936b37 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..66ab6f29 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/android/support/support-v4/26.1.0/support-v4-26.1.0.pom @@ -0,0 +1,61 @@ + + + 4.0.0 + com.android.support + support-v4 + 26.1.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 + + + + com.android.support + support-compat + 26.1.0 + compile + + + com.android.support + support-media-compat + 26.1.0 + compile + + + com.android.support + support-core-utils + 26.1.0 + compile + + + com.android.support + support-core-ui + 26.1.0 + compile + + + com.android.support + support-fragment + 26.1.0 + compile + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..0dbd4b7f Binary files /dev/null and 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 differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..cc029606 --- /dev/null +++ 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 @@ -0,0 +1,38 @@ + + + 4.0.0 + com.android.support + support-vector-drawable + 24.0.0 + aar + Android Support VectorDrawable + Android Support VectorDrawable + 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 + + + + com.android.support + support-v4 + 24.0.0 + aar + compile + + + diff --git a/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 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 new file mode 100644 index 00000000..619d527a Binary files /dev/null and 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 differ diff --git a/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 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 new file mode 100644 index 00000000..6bc74025 --- /dev/null +++ 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 @@ -0,0 +1,9 @@ + + 4.0.0 + com.google.android.gms + play-services-basement-license + 12.0.0 + aar + play-services-basement-license +Android Software Development Kit Licensehttps://developer.android.com/studio/terms.htmlrepo + diff --git a/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 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 new file mode 100644 index 00000000..7717bc38 Binary files /dev/null and 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 differ diff --git a/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 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 new file mode 100644 index 00000000..42996dbb --- /dev/null +++ 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 @@ -0,0 +1,25 @@ + + 4.0.0 + com.google.android.gms + play-services-basement + 12.0.0 + aar + + + com.android.support + support-v4 + 26.1.0 + compile + aar + + + com.google.android.gms + play-services-basement-license + 12.0.0 + runtime + aar + + + play-services-basement +Android Software Development Kit Licensehttps://developer.android.com/studio/terms.htmlrepo + diff --git a/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 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 new file mode 100644 index 00000000..5e633aaa Binary files /dev/null and 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 differ diff --git a/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 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 new file mode 100644 index 00000000..2250f486 --- /dev/null +++ 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 @@ -0,0 +1,25 @@ + + + 4.0.0 + com.google.android.gms + play-services-basement + 15.0.0 + aar + + + com.android.support + support-v4 + 26.1.0 + compile + aar + + + play-services-basement + + + Android Software Development Kit License + https://developer.android.com/studio/terms.html + repo + + + 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/AndroidResolver/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 new file mode 100644 index 00000000..43495773 Binary files /dev/null and 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 differ diff --git a/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 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 new file mode 100644 index 00000000..a8a8592e --- /dev/null +++ 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 @@ -0,0 +1,17 @@ + + 4.0.0 + com.google.android.gms + play-services-basement + 9.8.0 + aar + + + com.android.support + support-v4 + 24.0.0 + compile + aar + + + Android Software Development Kit Licensehttps://developer.android.com/studio/terms.htmlrepo + diff --git a/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 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 new file mode 100644 index 00000000..9f6c81d0 Binary files /dev/null and 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 differ diff --git a/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 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 new file mode 100644 index 00000000..6e528f4d --- /dev/null +++ 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 @@ -0,0 +1,9 @@ + + 4.0.0 + com.google.android.gms + play-services-tasks-license + 12.0.0 + aar + play-services-tasks-license +Android Software Development Kit Licensehttps://developer.android.com/studio/terms.htmlrepo + diff --git a/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 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 new file mode 100644 index 00000000..7e06577c Binary files /dev/null and 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 differ diff --git a/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 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 new file mode 100644 index 00000000..159538de --- /dev/null +++ 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 @@ -0,0 +1,25 @@ + + 4.0.0 + com.google.android.gms + play-services-tasks + 12.0.0 + aar + + + com.google.android.gms + play-services-basement + 12.0.0 + compile + aar + + + com.google.android.gms + play-services-tasks-license + 12.0.0 + runtime + aar + + + play-services-tasks +Android Software Development Kit Licensehttps://developer.android.com/studio/terms.htmlrepo + diff --git a/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 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 new file mode 100644 index 00000000..885b4717 Binary files /dev/null and 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 differ diff --git a/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 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 new file mode 100644 index 00000000..16dbd62a --- /dev/null +++ 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 @@ -0,0 +1,25 @@ + + + 4.0.0 + com.google.android.gms + play-services-tasks + 15.0.0 + aar + + + com.google.android.gms + play-services-basement + [15.0.0] + compile + aar + + + play-services-tasks + + + Android Software Development Kit License + https://developer.android.com/studio/terms.html + repo + + + diff --git a/source/AndroidResolver/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 new file mode 100755 index 00000000..bf42bfb2 --- /dev/null +++ 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 @@ -0,0 +1,13 @@ + + 4.0.0 + com.google.firebase + firebase-app-unity + 4.3.0 + aar + + + + + diff --git a/source/AndroidResolver/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 new file mode 100755 index 00000000..61809930 Binary files /dev/null and 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 differ diff --git a/source/AndroidResolver/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 new file mode 100755 index 00000000..3985a16c --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml @@ -0,0 +1,10 @@ + + com.google.firebase + firebase-app-unity + + 4.3.0 + 4.3.0 + + + + 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/AndroidResolver/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 new file mode 100644 index 00000000..f9503a74 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..2c9a00f2 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/1.0.0/common-impl-1.0.0.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr + common-impl + 1.0.0 + aar + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..7daf9c77 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..043676b8 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.1.0/common-impl-2.1.0.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr + common-impl + 2.1.0 + aar + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..22637b0a Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..71e7ba77 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.3.0/common-impl-2.3.0.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr + common-impl + 2.3.0 + aar + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..33d90051 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..7817c6f5 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/2.5.0/common-impl-2.5.0.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr + common-impl + 2.5.0 + aar + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..7a769a97 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..8e059a17 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.0/common-impl-3.0.0.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr + common-impl + 3.0.0 + aar + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..ded9e623 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..d96d216f --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.1/common-impl-3.0.1.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr + common-impl + 3.0.1 + aar + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..bd776322 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..1ece0f9f --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/3.0.2/common-impl-3.0.2.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr + common-impl + 3.0.2 + aar + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..72b6b2da Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..3dd449a3 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/4.0.0/common-impl-4.0.0.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr + common-impl + 4.0.0 + aar + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..54fa93db Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..79a6cd6b --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/5.0.0/common-impl-5.0.0.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr + common-impl + 5.0.0 + aar + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..2f7b5af6 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common-impl/maven-metadata.xml @@ -0,0 +1,21 @@ + + org.test.psr + common-impl + + 5.0.0 + + 1.0.0 + 2.1.0 + 2.3.0 + 2.5.0 + 3.0.0 + 3.0.1 + 3.0.2 + 4.0.0 + 5.0.0 + + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..f86cb12f Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..58ef301d --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/1.0.1/common-1.0.1.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + common + 1.0.1 + aar + + + org.test.psr + common-impl + [1.0.0] + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..e9f5d78b Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..75b2643e --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.2.1/common-2.2.1.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + common + 2.2.1 + aar + + + org.test.psr + common-impl + [2.1.0,2.2.0) + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..4d2970eb Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..6af0a578 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.4.0/common-2.4.0.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + common + 2.4.0 + aar + + + org.test.psr + common-impl + [2.3.0,2.4.0) + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..20c42a2f Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..7ab6b42c --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/2.5.0/common-2.5.0.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + common + 2.5.0 + aar + + + org.test.psr + common-impl + [2.5.0] + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..7cb792ce Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..f7d2251f --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.1/common-3.0.1.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + common + 3.0.1 + aar + + + org.test.psr + common-impl + [3.0.0,4.0.0) + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..dd86ee07 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..030ec02b --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.2/common-3.0.2.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + common + 3.0.2 + aar + + + org.test.psr + common-impl + [3.0.1] + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..dd07da4e Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..da9db014 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/3.0.3/common-3.0.3.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + common + 3.0.3 + aar + + + org.test.psr + common-impl + [3.0.2] + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..ce008f6b Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..16fbb11a --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/4.0.1/common-4.0.1.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + common + 4.0.1 + aar + + + org.test.psr + common-impl + [4.0.0,5.0.0) + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..cc6345c4 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..e7cec2b8 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/5.0.1/common-5.0.1.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + common + 5.0.1 + aar + + + org.test.psr + common-impl + [5.0.0] + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..78742dbd --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/common/maven-metadata.xml @@ -0,0 +1,21 @@ + + org.test.psr + common + + 5.0.1 + + 1.0.1 + 2.2.1 + 2.4.0 + 2.5.0 + 3.0.1 + 3.0.2 + 3.0.3 + 4.0.1 + 5.0.1 + + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..59a1f33a Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..338ebfaa --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/1.2.3/common-1.2.3.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr.locked + common + 1.2.3 + aar + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..3f4ce466 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/common/maven-metadata.xml @@ -0,0 +1,13 @@ + + org.test.psr.locked + common + + 1.2.3 + + 1.2.3 + + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..6d702452 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..072c5d77 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.2.3/input-1.2.3.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr.locked + input + 1.2.3 + aar + + + org.test.psr.locked + common + [1.2.3] + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..1910a86e Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..8db7b979 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/1.5.0/input-1.5.0.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr.locked + input + 1.5.0 + aar + + + org.test.psr.locked + new-common + [1.5.0] + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..82908db9 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/input/maven-metadata.xml @@ -0,0 +1,14 @@ + + org.test.psr.locked + input + + 1.5.0 + + 1.2.3 + 1.5.0 + + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..19bf75ff Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..988fd4f2 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/1.5.0/new-common-1.5.0.pom @@ -0,0 +1,13 @@ + + 4.0.0 + org.test.psr.locked + new-common + 1.5.0 + aar + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..7c582550 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/new-common/maven-metadata.xml @@ -0,0 +1,13 @@ + + org.test.psr.locked + new-common + + 1.5.0 + + 1.5.0 + + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..7898f0ed Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..79c37513 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/1.5.0/output-1.5.0.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr.locked + output + 1.5.0 + aar + + + org.test.psr.locked + new-common + [1.5.0] + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..c79a54a6 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/locked/output/maven-metadata.xml @@ -0,0 +1,13 @@ + + org.test.psr.locked + output + + 1.5.0 + + 1.5.0 + + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..2127804f Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..feedad49 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/1.0.2/pull-1.0.2.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + pull + 1.0.2 + aar + + + org.test.psr + common + [1.0.1] + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..2b71f508 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..b02c42f9 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/2.0.3/pull-2.0.3.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + pull + 2.0.3 + aar + + + org.test.psr + common-impl + [2.3.0,2.4.0) + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..f60ee5f7 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..c80111aa --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/6.0.1/pull-6.0.1.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + pull + 6.0.1 + aar + + + org.test.psr + common-impl + [4.0.0,5.0.0) + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..717c3944 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/pull/maven-metadata.xml @@ -0,0 +1,15 @@ + + org.test.psr + pull + + 6.0.1 + + 1.0.2 + 2.0.3 + 6.0.1 + + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..e1549c18 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..8a88fe19 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/1.0.3/push-1.0.3.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + push + 1.0.3 + aar + + + org.test.psr + common-impl + [1.0.0] + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..bea7710e Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..ac3f1bc1 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.2/push-2.0.2.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + push + 2.0.2 + aar + + + org.test.psr + common + [2.2.+,2.2.8] + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..8273a277 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..49afe6e9 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/2.0.4/push-2.0.4.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + push + 2.0.4 + aar + + + org.test.psr + common + [2.4.0,2.5.0) + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..8fab4f68 Binary files /dev/null and b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.aar differ diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..aebdcac8 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/5.0.1/push-5.0.1.pom @@ -0,0 +1,17 @@ + + 4.0.0 + org.test.psr + push + 5.0.1 + aar + + + org.test.psr + common + [3.0.0,4.0.0) + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..dd6f8087 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/push/maven-metadata.xml @@ -0,0 +1,16 @@ + + org.test.psr + push + + 5.0.2 + + 1.0.3 + 2.0.2 + 2.0.4 + 5.0.1 + + + + + + diff --git a/source/AndroidResolver/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 new file mode 100644 index 00000000..83f487b6 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/readme.txt @@ -0,0 +1,56 @@ +This directory contains Maven packages components to test dependency resolution. + +The following test components are present: + +Test cases: + - Case 1: pull A requires common C, push B requires common-impl D + common C is compatible with common-impl D + - Case 2: pull A requires common C, push B requires common-impl D + common C is incompatible with common-impl D + Version of common X and common-impl Y exist that are compatible with + both pull A and push B. + - Case 3: pull A requires common C, push B requires common-impl D + common C is incompatible with common-impl D + No major version of common and common-impl exists that are compatible with + both pull A and push B. + - Case 4: common A requires common-impl C, common B requires common-impl D + C & D are not compatible with each other. The highest version of common needs + to be selected. + +The following section describes the set of artifacts in the form, +group artifact version 'dependencies': +==== +org.test.psr common-impl 1.0.0 '' +org.test.psr common 1.0.1 'org.test.psr:common-impl:[1.0.0]' +org.test.psr pull 1.0.2 'org.test.psr:common:[1.0.1]' +org.test.psr push 1.0.3 'org.test.psr:common-impl:[1.0.0]' + +org.test.psr common-impl 2.1.0 '' +org.test.psr common 2.2.1 'org.test.psr:common-impl:[2.1.0,2.2.0)' +org.test.psr push 2.0.2 'org.test.psr:common:[2.2.+,2.2.8]' +org.test.psr common-impl 2.3.0 '' +org.test.psr common 2.4.0 'org.test.psr:common-impl:[2.3.0,2.4.0)' +org.test.psr pull 2.0.3 'org.test.psr:common-impl:[2.3.0,2.4.0)' +org.test.psr common-impl 2.5.0 '' +org.test.psr common 2.5.0 'org.test.psr:common-impl:[2.5.0]' +org.test.psr push 2.0.4 'org.test.psr:common:[2.4.0,2.5.0)' + +org.test.psr common-impl 3.0.0 '' +org.test.psr common 3.0.1 'org.test.psr:common-impl:[3.0.0,4.0.0)' +org.test.psr common-impl 4.0.0 '' +org.test.psr common 4.0.1 'org.test.psr:common-impl:[4.0.0,5.0.0)' +org.test.psr common-impl 5.0.0 '' +org.test.psr common 5.0.1 'org.test.psr:common-impl:[5.0.0]' +org.test.psr push 5.0.1 'org.test.psr:common:[3.0.0,4.0.0)' +org.test.psr pull 6.0.1 'org.test.psr:common-impl:[4.0.0,5.0.0)' + +org.test.psr common-impl 3.0.1 '' +org.test.psr common-impl 3.0.2 '' +org.test.psr common 3.0.2 'org.test.psr:common-impl:[3.0.1]' +org.test.psr common 3.0.3 'org.test.psr:common-impl:[3.0.2]' + +org.test.psr.locked common 1.2.3 '' +org.test.psr.locked input 1.2.3 'org.test.psr.locked:common:[1.2.3]' +org.test.psr.locked new-common 1.5.0 '' +org.test.psr.locked input 1.5.0 'org.test.psr.locked:new-common:[1.5.0]' +org.test.psr.locked output 1.5.0 'org.test.psr.locked:new-common:[1.5.0]' diff --git a/source/AndroidResolver/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 new file mode 100755 index 00000000..e19bfeb0 --- /dev/null +++ b/source/AndroidResolver/scripts/download_artifacts_test_assets/m2repository/org/test/psr/update_from_readme.sh @@ -0,0 +1,49 @@ +#!/bin/bash -exu + +declare -r CURRENT_DIR="$(cd "$(dirname "$0")"; pwd)" +: ${REPO_DIR="$(cd "${CURRENT_DIR}/../../../"; pwd)"} +: ${SCRIPTS_DIR="$(cd "${REPO_DIR}/../../"; pwd)"} +: ${FILE_TO_PROCESS="${CURRENT_DIR}/readme.txt"} + +help() { + echo "\ +Usage: $(basename $0) + +Generate a maven repository from lines following the '===' +delimiter in ${FILE_TO_PROCESS}. + +Each line consists of: +repo group artifact version dependencies + +Each line is passed to generate_test_maven_package.sh as arguments +to create a test maven artifact. + +This script can be configured using the following variables: +REPO_DIR: + Root directory of the maven repository to modify. + Defaults to ${ROOT_DIR} +SCRIPTS_DIR: + Directory which contains the generate_test_maven_package.sh script. + Defaults to ${SCRIPTS_DIR} +FILE_TO_PROCESS: + File to process, defaults to ${FILE_TO_PROCESS}. +" >&2 + exit 1 +} + +main() { + if [[ ! -e "${FILE_TO_PROCESS}" ]]; then + echo "Unable to find ${FILE_TO_PROCESS} in the current directory" >&2 + help + fi + + cd "${CURRENT_DIR}" + find "${CURRENT_DIR}" -mindepth 1 -maxdepth 1 -type d | xargs rm -rf + # Extract all non-blank lines from the file to process + awk '{ if (p) { print $0 } } /^===/ { p = 1 }' "${FILE_TO_PROCESS}" | \ + grep -v '^$' | \ + sed 's@^@'"${SCRIPTS_DIR}"'/generate_test_maven_package.sh '"${REPO_DIR}"' @' \ + | /bin/bash -xeu +} + +main 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/AndroidResolver/scripts/generate_test_maven_package.sh b/source/AndroidResolver/scripts/generate_test_maven_package.sh new file mode 100755 index 00000000..0b68795f --- /dev/null +++ b/source/AndroidResolver/scripts/generate_test_maven_package.sh @@ -0,0 +1,152 @@ +#!/bin/bash -eu +# +# Creates a test maven package. + +declare -r MAVEN_PACKAGE_METADATA_VERSION_TEMPLATE="\ + VERSION" + +declare -r MAVEN_PACKAGE_METADATA_TEMPLATE="\ + + GROUP + ARTIFACT + + RELEASE_VERSION + +VERSIONS + + + + +" + +declare -r MAVEN_POM_DEPENDENCY_TEMPLATE="\ + + GROUP + ARTIFACT + VERSION + " + +declare -r MAVEN_POM_TEMPLATE="\ + + 4.0.0 + GROUP + ARTIFACT + VERSION + aar + +DEPENDENCIES + + +" + +declare -r ANDROID_MANIFEST_TEMPLATE='\ + + + + + + + +' + +main() { + if [[ $# -ne 5 ]]; then + echo "\ +Usage: $(basename $0) repo group package version 'dependencies' + +Generates a local test Maven repository artifact including Maven POM and +group metadata. The generated artifact is a unique Android AAR. + +repo: + Local repository directory +group: + Group for the artifact (e.g org.something.somethingelse) +package: + Name of the package to create. +version: + Version of the package to create. +dependencies: + Space separated list of dependencies to write into the package POM in the + form group:artifact:version where version is a Maven version expression. +" >&2 + exit 1 + fi + + local -r repo="${1}" + local -r group="${2}" + local -r artifact="${3}" + local -r version="${4}" + local -r dependencies=(${5}) + + local -r package_dir="${repo}/${group//.//}/${artifact}" + mkdir -p "${package_dir}" + local -r artifact_dir="${package_dir}/${version}" + mkdir -p "${artifact_dir}" + + local -r maven_metadata="${package_dir}/maven-metadata.xml" + local versions_xml="" + for current_version in \ + $(find "${package_dir}" -mindepth 1 -maxdepth 1 -type d | \ + sort -n | \ + sed "s@${package_dir}/@@"); do + versions_xml="${versions_xml}\ +$(echo "${MAVEN_PACKAGE_METADATA_VERSION_TEMPLATE/VERSION/${current_version}}")\\ +" + done + echo "${MAVEN_PACKAGE_METADATA_TEMPLATE}" | \ + sed "s@GROUP@${group}@;\ + s@ARTIFACT@${artifact}@;\ + s@RELEASE_VERSION@${version}@; + s@VERSIONS@${versions_xml}@;" > \ + "${maven_metadata}" + + local -r artifact_file_basename="${artifact_dir}/${artifact}-${version}" + local -r pom="${artifact_file_basename}.pom" + local dependency_xml="" + if [[ ${#dependencies[@]} -gt 0 ]]; then + for dependency in "${dependencies[@]}"; do + local tokens=(${dependency//:/ }) + if [[ ${#tokens[@]} -ne 3 ]]; then + echo "Ignoring invalid dependency ${dependency}" >&2 + continue + fi + local xml_block=$(\ + echo "${MAVEN_POM_DEPENDENCY_TEMPLATE}" | \ + sed "s@GROUP@${tokens[0]}@;\ + s@ARTIFACT@${tokens[1]}@;\ + s@VERSION@${tokens[2]}@;") + dependency_xml="${dependency_xml}${xml_block}" + done + fi + + echo "${MAVEN_POM_TEMPLATE}" | \ + sed "s@GROUP@${group}@;\ + s@ARTIFACT@${artifact}@;\ + s@VERSION@${version}@;\ + s@DEPENDENCIES@$(echo "${dependency_xml}" | \ + sed 's/$/\\/g')\@;" | \ + sed 's/\\$//' > "${pom}" + + local -r aar_tmp_dir="${artifact_dir}/tmp" + mkdir -p "${aar_tmp_dir}" + pushd "${aar_tmp_dir}" >/dev/null + echo "${ANDROID_MANIFEST_TEMPLATE}" | \ + sed "s@GROUP@${group}@; + s@ARTIFACT@${artifact}@; + s@VERSION@${version}@;" > AndroidManifest.xml + touch R.txt + touch proguard.txt + zip classes.jar R.txt + zip -d classes.jar R.txt + zip "../$(basename "${artifact_file_basename}.aar")" * + popd >/dev/null + rm -rf "${aar_tmp_dir}" +} + +main "$@" diff --git a/source/AndroidResolver/scripts/gradle-template.zip b/source/AndroidResolver/scripts/gradle-template.zip new file mode 100644 index 00000000..db181f66 Binary files /dev/null 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/AndroidResolver/src/AndroidAbis.cs b/source/AndroidResolver/src/AndroidAbis.cs new file mode 100644 index 00000000..20b12c3d --- /dev/null +++ b/source/AndroidResolver/src/AndroidAbis.cs @@ -0,0 +1,329 @@ +// +// Copyright (C) 2018 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.Reflection; + +namespace GooglePlayServices { + +/// +/// Provides access to Android ABI settings across different Unity versions. +/// +internal class AndroidAbis { + /// + /// Determines and caches properties used to configure Android ABI selection. + /// + private class PropertyConfiguration { + /// + /// The mode used to select Android ABIs by Unity. + /// + 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, x86_64 or arm64 builds. + } + + /// + /// Mode used to select Android ABIs. + /// + public Mode SelectionMode { private set; get; } + + /// + /// Property to read and set ABIs, this is null in Unity 4.+. + /// + public PropertyInfo Property { private set; get; } + + /// + /// Enum type used by the member, this is null in Unity 4.+. + /// + public Type EnumType { private set; get; } + + /// + /// 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. + /// + public Dictionary SupportedAbiToAbiEnumValue { private set; get; } + + /// + /// Determine and cache the method of ABI selection used by the current version of Unity. + /// + public PropertyConfiguration() { + // Unity 2017.4 and above should support targetArchitectures. + Property = typeof(UnityEditor.PlayerSettings.Android).GetProperty( + "targetArchitectures"); + EnumType = Google.VersionHandler.FindClass("UnityEditor", + "UnityEditor.AndroidArchitecture"); + if (Property != null && EnumType != null) { + SelectionMode = Mode.AnyOfArmX86Arm64; + 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. + Property = typeof(UnityEditor.PlayerSettings.Android).GetProperty("targetDevice"); + EnumType = Google.VersionHandler.FindClass("UnityEditor", + "UnityEditor.AndroidTargetDevice"); + if (Property != null && EnumType != null) { + SelectionMode = Mode.OneOfArmX86Fat; + SupportedAbiToAbiEnumValue = new Dictionary() { + {"armeabi-v7a", "ARMv7"}, + {"x86", "x86"} + }; + return; + } + // Unity 4.0 and above only support fat builds. + SelectionMode = Mode.FatOnly; + SupportedAbiToAbiEnumValue = new Dictionary() { + {"armeabi-v7a", ""}, + {"x86", ""} + }; + } + + // 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. + /// + private HashSet abis; + + /// + /// Create the default ABI set. + /// + public AndroidAbis() { + abis = new HashSet(Supported); + } + + /// + /// Create a selected set of ABIs from a set. + /// + /// Set of ABI strings. + public AndroidAbis(IEnumerable abisSet) { + abis = new HashSet(abisSet); + } + + /// + /// Create a set of ABIs from a comma separated set of ABI strings. + /// + /// Comma separated set of ABI strings. + public AndroidAbis(string abiString) { + if (String.IsNullOrEmpty(abiString)) { + abis = new HashSet(Supported); + } else { + abis = new HashSet(); + foreach (var abi in abiString.Split(new [] { ',' })) { + abis.Add(abi.Trim()); + } + } + } + + /// + /// Convert the set of ABIs to a string. + /// + public override string ToString() { + var abiList = (new List(abis)); + abiList.Sort(); + return String.Join(",", abiList.ToArray()); + } + + /// + /// Get the set of ABIs as a set. + /// + /// Set of ABIs. + public HashSet ToSet() { return new HashSet(abis); } + + /// + /// 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 otherObj = obj as AndroidAbis; + return otherObj != null && abis.SetEquals(otherObj.abis); + } + + /// + /// Generate a hash of this object. + /// + /// Hash of this object. + public override int GetHashCode() { return abis.GetHashCode(); } + + /// + /// 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. + /// + private static Dictionary SupportedAbiToAbiEnumValue { + get { return PropertyConfiguration.Instance.SupportedAbiToAbiEnumValue; } + } + + /// + /// Get the supported set of Android ABIs for the current Unity version. + /// Each ABI string is an official Android ABI name and corresponds to the supported ABI + /// directory name under the "jni" folder in an APK or AAR. + /// + public static IEnumerable Supported { + get { return SupportedAbiToAbiEnumValue.Keys; } + } + + /// + /// Get the supported set of all possible Android ABIs, ignoring what Unity supports. + /// + public static IEnumerable AllSupported { + get { + return new [] { + "armeabi", "armeabi-v7a", "arm64-v8a", "x86", "x86_64", "mips", "mips64" + }; + } + } + + /// + /// Convert an enum value object to a ulong. + /// + /// 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. + return UInt64.Parse(String.Format("{0:D}", enumValueObject)); + } + + /// + /// Convert an enum value string to a ulong. + /// + /// Enum type to use to parse the string. + /// Enum string to convert. + private static ulong EnumValueStringToULong(Type enumType, string enumValueString) { + return EnumValueObjectToULong(Enum.Parse(enumType, enumValueString)); + } + + /// + /// 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) + /// + public static AndroidAbis Current { + set { + var propertyConfiguration = PropertyConfiguration.Instance; + var property = propertyConfiguration.Property; + var enumType = propertyConfiguration.EnumType; + var supportedAbis = SupportedAbiToAbiEnumValue; + var abiSet = value.ToSet(); + if (propertyConfiguration.SelectionMode == + PropertyConfiguration.Mode.AnyOfArmX86Arm64) { + // Convert selected ABIs to a flags enum. + ulong enumValue = 0; + foreach (var abi in supportedAbis) { + if (abiSet.Contains(abi.Key)) { + // It's not possible to trivially cast a flag enum value to an int + // so perform the conversion via a string. + ulong enumValueInt = EnumValueStringToULong(enumType, abi.Value); + enumValue |= enumValueInt; + } + } + property.SetValue(null, Enum.ToObject(enumType, enumValue), null); + } else if (propertyConfiguration.SelectionMode == + PropertyConfiguration.Mode.OneOfArmX86Fat) { + // Filter the requested ABIs by those supported. + abiSet.IntersectWith(supportedAbis.Keys); + if (abiSet.Count == 0) return; + // If more than one ABI is specified, select all. + property.SetValue( + null, + Enum.ToObject( + enumType, + EnumValueStringToULong( + enumType, + abiSet.Count > 1 ? "FAT" : + supportedAbis[(new List(abiSet))[0]])), null); + } else { + UnityEngine.Debug.LogWarning( + "The current version of Unity does not support targeting a " + + "specific set of Android ABIs."); + } + } + + get { + var propertyConfiguration = PropertyConfiguration.Instance; + var property = propertyConfiguration.Property; + var enumType = propertyConfiguration.EnumType; + var supportedAbis = SupportedAbiToAbiEnumValue; + var selectedAbis = new HashSet(); + if (propertyConfiguration.SelectionMode == + PropertyConfiguration.Mode.AnyOfArmX86Arm64) { + // Convert flags enum value to a set of ABI names. + ulong enumValueInt = EnumValueObjectToULong(property.GetValue(null, null)); + foreach (var abi in supportedAbis) { + if ((enumValueInt & EnumValueStringToULong(enumType, abi.Value)) != 0) { + selectedAbis.Add(abi.Key); + } + } + } else if (propertyConfiguration.SelectionMode == + PropertyConfiguration.Mode.OneOfArmX86Fat) { + // Convert enum value to an ABI name. + var abiName = Enum.GetName(enumType, property.GetValue(null, null)); + foreach (var abi in supportedAbis) { + if (abi.Value == abiName) { + selectedAbis.Add(abi.Key); + break; + } + } + } + return selectedAbis.Count == 0 ? new AndroidAbis() : new AndroidAbis(selectedAbis); + } + } + + /// + /// Get / set the target device ABIs using a string. + /// + public static string CurrentString { + set { Current = new AndroidAbis(value); } + get { return Current.ToString(); } + } +} + +} + + diff --git a/source/AndroidResolver/src/AndroidSdkManager.cs b/source/AndroidResolver/src/AndroidSdkManager.cs new file mode 100644 index 00000000..28df9b04 --- /dev/null +++ b/source/AndroidResolver/src/AndroidSdkManager.cs @@ -0,0 +1,1070 @@ +// +// Copyright (C) 2017 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; + using Google.JarResolver; + using System; + using System.Diagnostics; + using System.IO; + using System.Collections.Generic; + using System.Text.RegularExpressions; + + /// + /// Subset of Android SDK package metadata required for installation. + /// + internal class AndroidSdkPackageNameVersion { + /// Converts and old "android" package manager package name to a new "sdkmanager" package + /// name. + private static Dictionary OLD_TO_NEW_PACKAGE_NAME_PREFIX = + new Dictionary { { "extra;", "extras;" } }; + /// List package name components to not convert hyphens / semi-colons. + /// Some package names contain hyphens and therefore can't simply be converted by + /// replacing - with ;. This list of name components is used to preserved when + /// converting between new and legacy package names. + /// This list was dervied from: + /// sdkmanager --verbose --list | \ + /// grep -vE '^(Info:| |---)' | \ + /// grep '-' | \ + /// tr ';' '\n' | \ + /// sort | \ + /// uniq | \ + /// grep '-' | \ + /// sed 's/.*/"&",/' + private static List PRESERVED_PACKAGE_NAME_COMPONENTS = new List { + new Regex("\\d+.\\d+.\\d+-[a-zA-Z0-9]+"), + new Regex("add-ons"), + new Regex("addon-google_apis-google-\\d+"), + new Regex("android-\\d+"), + new Regex("arm64-v8a"), + new Regex("armeabi-v7a"), + new Regex("build-tools"), + new Regex("constraint-layout"), + new Regex("constraint-layout-solver"), + new Regex("ndk-bundle"), + new Regex("platform-tools"), + new Regex("system-images"), + new Regex("support-[a-zA-Z0-9]+"), + }; + + /// + /// Name of the package. + /// + public string Name { set; get; } + + /// + /// Escape components that should not be converted by LegacyName. + /// + /// Escaped package name. + private string EscapeComponents(string packageName) { + foreach (var componentRegex in PRESERVED_PACKAGE_NAME_COMPONENTS) { + var match = componentRegex.Match(packageName); + if (match.Success) { + var prefix = packageName.Substring(0, match.Index); + var postfix = packageName.Substring(match.Index + match.Length); + // Exclamation marks are guaranteed - at the moment - to not be + // part of a package name / path. + packageName = prefix + match.Value.Replace("-", "!") + postfix; + } + } + return packageName; + } + + /// + /// Un-escaped components that should not be converted by LegacyName. + /// + /// Un-escaped package name. + private string UnescapeComponents(string packageName) { + return packageName.Replace("!", "-"); + } + + /// + /// Convert to / from a legacy package name. + /// + public string LegacyName { + set { + var packageName = UnescapeComponents(EscapeComponents(value).Replace("-", ";")); + foreach (var kv in OLD_TO_NEW_PACKAGE_NAME_PREFIX) { + if (packageName.StartsWith(kv.Key)) { + packageName = kv.Value + packageName.Substring(kv.Key.Length); + break; + } + } + Name = packageName; + } + + get { + var packageName = Name; + foreach (var kv in OLD_TO_NEW_PACKAGE_NAME_PREFIX) { + if (packageName.StartsWith(kv.Value)) { + packageName = kv.Key + packageName.Substring(kv.Value.Length); + break; + } + } + return packageName.Replace(";", "-"); + } + } + + /// + /// Convert to / from a package path to name. + /// + /// Android SDK package names are derived from their path relative to the SDK directory. + public string Path { + set { + Name = value.Replace("\\", "/").Replace("/", ";"); + } + get { + return Name.Replace(";", System.IO.Path.PathSeparator.ToString()); + } + } + + /// + /// String representation of the package version. + /// + public string VersionString { set; get; } + + /// + /// 64-bit integer representation of the package version. + /// + public long Version { get { return ConvertVersionStringToInteger(VersionString); } } + + /// + /// Get a string representation of this object. + /// + public override string ToString() { + return String.Format("{0} ({1})", Name, VersionString); + } + + /// + /// Hash the name of this package. + /// + public override int GetHashCode() { + return Name.GetHashCode(); + } + + /// + /// Compares two package names. + /// + /// Object to compare with. + /// true if both objects have the same name, false otherwise. + public override bool Equals(System.Object obj) { + var pkg = obj as AndroidSdkPackageNameVersion; + return pkg != null && pkg.Name == Name; + } + + /// + /// Convert an N component version string into an integer. + /// + /// Version string to convert. + /// Value to multiply each component by. + /// An integer representation of the version string. + public static long ConvertVersionStringToInteger(string versionString, + long componentMultiplier = 1000000) { + if (String.IsNullOrEmpty(versionString)) return 0; + var components = versionString.Split(new [] { '.' }); + long versionInteger = 0; + long currentMultiplier = 1; + Array.Reverse(components); + foreach (var component in components) { + long componentInteger = 0; + try { + componentInteger = Convert.ToInt64(component); + } catch (FormatException) { + PlayServicesResolver.Log( + String.Format("Unable to convert version string {0} to " + + "integer value", versionString), + level: LogLevel.Warning); + return 0; + } + versionInteger += (componentInteger * currentMultiplier); + currentMultiplier *= componentMultiplier; + } + return versionInteger; + } + + /// + /// Convert a list of package name / versions to a bulleted string list. + /// + /// List of packages to write to a string. + /// Bulleted list of package name / versions. + public static string ListToString( + IEnumerable packages) { + var packageAndVersion = new List(); + foreach (var pkg in packages) { + packageAndVersion.Add(String.Format( + "* {0} {1}", pkg.Name, + !String.IsNullOrEmpty(pkg.VersionString) ? + String.Format("({0})", pkg.VersionString) : "")); + } + return String.Join("\n", packageAndVersion.ToArray()); + } + } + + /// + /// Describes an Android SDK package. + /// + internal class AndroidSdkPackage : AndroidSdkPackageNameVersion { + + /// + /// Human readable description of the package. + /// + public string Description { set; get; } + + /// + /// Whether the package is installed. + /// + public bool Installed { set; get; } + + /// + /// Read package metadata from the source.properties file within the specified directory. + /// + /// Android SDK directory to query. + /// Directory containing the package relative to + /// sdkDirectory. + public static AndroidSdkPackage ReadFromSourceProperties(string sdkDirectory, + string packageDirectory) { + var propertiesPath = System.IO.Path.Combine( + sdkDirectory, System.IO.Path.Combine(packageDirectory, "source.properties")); + string propertiesText = null; + try { + propertiesText = File.ReadAllText(propertiesPath); + } catch (Exception e) { + PlayServicesResolver.Log(String.Format("Unable to read {0}\n{1}\n", + propertiesPath, e.ToString()), + level: LogLevel.Verbose); + return null; + } + // Unfortunately the package name is simply based upon the path within the SDK. + var sdkPackage = new AndroidSdkPackage { Path = packageDirectory }; + const string VERSION_FIELD_NAME = "Pkg.Revision="; + const string DESCRIPTION_FIELD_NAME = "Pkg.Desc="; + foreach (var rawLine in CommandLine.SplitLines(propertiesText)) { + var line = rawLine.Trim(); + // Ignore comments. + if (line.StartsWith("#")) continue; + // Parse fields + if (line.StartsWith(VERSION_FIELD_NAME)) { + sdkPackage.VersionString = line.Substring(VERSION_FIELD_NAME.Length); + } else if (line.StartsWith(DESCRIPTION_FIELD_NAME)) { + sdkPackage.Description = line.Substring(DESCRIPTION_FIELD_NAME.Length); + } + } + return sdkPackage; + } + } + + /// + /// Collection of AndroidSdkPackage instances indexed by package name. + /// + internal class AndroidSdkPackageCollection { + private Dictionary> packages = + new Dictionary>(); + + /// + /// Get the set of package names in the collection. + /// + public List PackageNames { + get { return new List(packages.Keys); } + } + + /// + /// Get the list of package metadata by package name. + /// + /// List of package metadata. + public List this[string packageName] { + get { + List packageList = null; + if (!packages.TryGetValue(packageName, out packageList)) { + packageList = new List(); + packages[packageName] = packageList; + } + return packageList; + } + } + + /// + /// Get the most recent available version of a specified package. + /// + /// The package if it's available, null otherwise. + public AndroidSdkPackage GetMostRecentAvailablePackage(string packageName) { + var packagesByVersion = new SortedDictionary(); + foreach (var sdkPackage in this[packageName]) { + packagesByVersion[sdkPackage.Version] = sdkPackage; + } + if (packagesByVersion.Count == 0) return null; + AndroidSdkPackage mostRecentPackage = null; + foreach (var pkg in packagesByVersion.Values) mostRecentPackage = pkg; + return mostRecentPackage; + } + + /// + /// Get installed package metadata by package name. + /// + /// The package if it's installed, null otherwise. + public AndroidSdkPackage GetInstalledPackage(string packageName) { + foreach (var sdkPackage in this[packageName]) { + if (sdkPackage.Installed) return sdkPackage; + } + return null; + } + } + + /// + /// Interface used to interact with Android SDK managers. + /// + internal interface IAndroidSdkManager { + /// + /// Use the package manager to retrieve the set of installed and available packages. + /// + /// Called when the query is complete. + void QueryPackages(Action complete); + + /// + /// Install a set of packages. + /// + /// Set of packages to install / upgrade. + /// Called when installation is complete. + void InstallPackages(HashSet packages, + Action complete); + } + + // Answers Android SDK manager license questions. + internal class LicenseResponder : CommandLine.LineReader { + // String to match in order to respond. + private string question; + // Response to provide to the question. + private string response; + + /// + /// Initialize the class to respond "yes" or "no" to license questions. + /// + /// Question to respond to. + /// Response to provide. + public LicenseResponder(string question, string response) { + this.question = question; + this.response = response; + LineHandler += CheckAndRespond; + } + + // Respond license questions with the "response". + public void CheckAndRespond(Process process, StreamWriter stdin, + CommandLine.StreamData data) { + if (process.HasExited) return; + + if ((data.data != null && data.text.Contains(question)) || + CommandLine.LineReader.Aggregate(GetBufferedData(0)).text.Contains(question)) { + Flush(); + // Ignore I/O exceptions as this could race with the process exiting. + try { + foreach (byte b in System.Text.Encoding.UTF8.GetBytes( + response + System.Environment.NewLine)) { + stdin.BaseStream.WriteByte(b); + } + stdin.BaseStream.Flush(); + } catch (System.IO.IOException) { + } + } + } + } + + /// + /// Utility methods for implementations of IAndroidSdkManager. + /// + internal class SdkManagerUtil { + + /// + /// Message displayed if a package query operation fails. + /// + const string PACKAGES_MISSING = + "Unable to determine which Android packages are installed.\n{0}"; + + /// + /// Title of the installation dialog. + /// + private const string DIALOG_TITLE = "Installing Android SDK packages"; + + /// + /// Use the package manager to retrieve the set of installed and available packages. + /// + /// Tool to run. + /// Arguments to pass to the tool. + /// 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" + + "\n" + + "{0} {1}\n", + toolPath, toolArguments), + level: LogLevel.Verbose); + window.summaryText = "Getting Installed Android SDK packages."; + window.modal = false; + window.progressTitle = window.summaryText; + window.autoScrollToBottom = true; + window.logger = PlayServicesResolver.logger; + window.RunAsync( + toolPath, toolArguments, + (CommandLine.Result result) => { + window.Close(); + 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); + }, + maxProgressLines: 50); + window.Show(); + } + + /// + /// Retrieve package licenses, display license dialog and then install packages. + /// + /// Tool to run. + /// Arguments to pass to the tool. + /// List of package versions to install / upgrade. + /// License question to respond to. + /// String used to agree to a license. + /// String used to decline a license. + /// Regex which matches the line which is the start of a + /// license agreement. + /// Called when installation is complete. + public static void InstallPackages( + string toolPath, string toolArguments, + 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", + toolPath, toolArguments), + level: LogLevel.Verbose); + // Display the license retrieval dialog. + DisplayInstallLicenseDialog( + toolPath, toolArguments, true, + new LicenseResponder(licenseQuestion, licenseDecline), packages, + (CommandLine.Result licensesResult) => { + if (licensesResult.exitCode != 0) { + complete(false); + return; + } + // Get the license text. + var licensesLines = new List(); + bool foundLicenses = false; + foreach (var line in CommandLine.SplitLines(licensesResult.stdout)) { + foundLicenses = foundLicenses || licenseTextHeader.Match(line).Success; + if (foundLicenses) licensesLines.Add(line); + } + if (licensesLines.Count == 0) { + LogInstallLicenseResult(toolPath, toolArguments, false, packages, + licensesResult); + complete(true); + return; + } + // Display the license agreement dialog. + DisplayLicensesDialog( + String.Join("\n", licensesLines.ToArray()), + (bool agreed) => { + if (!agreed) { + complete(false); + return; + } + // Finally install the packages agreeing to the license questions. + DisplayInstallLicenseDialog( + toolPath, toolArguments, false, + new LicenseResponder(licenseQuestion, licenseAgree), packages, + (CommandLine.Result installResult) => { + complete(installResult.exitCode == 0); + }); + }); + }); + } + + /// + /// Log a message that describes the installation / license fetching operation. + /// + /// Tool that was executed. + /// Arguments to passed to the tool. + /// Whether the command is retrieving licenses. + /// List of package versions to install / upgrade. + /// Result of the tool's execution. + private static void LogInstallLicenseResult( + string toolPath, string toolArguments, bool retrievingLicenses, + IEnumerable packages, + CommandLine.Result toolResult) { + bool succeeded = toolResult.exitCode == 0; + if (!retrievingLicenses || !succeeded) { + var failedMessage = retrievingLicenses ? + "Failed to retrieve Android SDK package licenses.\n\n" + + "Aborted installation of the following packages:\n" : + "Android package installation failed.\n\n" + + "Failed when installing the following packages:\n"; + PlayServicesResolver.Log( + String.Format( + "{0}\n" + + "{1}\n\n" + + "{2}\n", + succeeded ? "Successfully installed Android packages.\n\n" : failedMessage, + 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"); + } + } + } + + /// + /// Open a install / license window and execute a command. + /// + /// Tool to run. + /// Arguments to pass to the tool. + /// Whether the command is retrieving licenses. + /// Responds to license questions. + /// List of package versions to install / upgrade. + /// Called when installation is complete. + private static void DisplayInstallLicenseDialog( + string toolPath, string toolArguments, bool retrievingLicenses, + LicenseResponder licenseResponder, + IEnumerable packages, + Action complete) { + var summary = retrievingLicenses ? + "Attempting Android SDK package installation..." : DIALOG_TITLE + "..."; + var window = CommandLineDialog.CreateCommandLineDialog(DIALOG_TITLE); + window.summaryText = summary; + window.modal = false; + window.bodyText = String.Format("{0} {1}\n\n", toolPath, toolArguments); + window.progressTitle = window.summaryText; + window.autoScrollToBottom = true; + window.logger = PlayServicesResolver.logger; + CommandLine.IOHandler ioHandler = null; + if (licenseResponder != null) ioHandler = licenseResponder.AggregateLine; + PlayServicesResolver.Log(String.Format("{0} {1}", toolPath, toolArguments), + level: LogLevel.Verbose); + window.RunAsync( + toolPath, toolArguments, + (CommandLine.Result result) => { + window.Close(); + LogInstallLicenseResult(toolPath, toolArguments, retrievingLicenses, packages, + result); + complete(result); + }, + ioHandler: ioHandler, + maxProgressLines: retrievingLicenses ? 250 : 500); + window.Show(); + } + + /// + /// Display license dialog. + /// + /// String containing the licenses to display. + /// Called when the user agrees / disagrees to the licenses. + private static void DisplayLicensesDialog(string licenses, Action complete) { + var window = CommandLineDialog.CreateCommandLineDialog(DIALOG_TITLE); + window.summaryText = "License agreement(s) required to install Android SDK packages"; + window.modal = false; + window.bodyText = licenses; + window.yesText = "agree"; + window.noText = "decline"; + window.result = false; + window.logger = PlayServicesResolver.logger; + window.Repaint(); + window.buttonClicked = (TextAreaDialog dialog) => { + window.Close(); + if (!dialog.result) { + complete(false); + return; + } + complete(true); + }; + window.Show(); + } + } + + /// + /// Interacts with the legacy Android SDK manager "android". + /// + internal class AndroidToolSdkManager : IAndroidSdkManager { + /// Name of the SDK manager command line tool. + public const string TOOL_NAME = "android"; + + /// + /// Extracts the package identifer from the SDK list output. + /// + private static Regex PACKAGE_ID_REGEX = new Regex( + "^id:\\W+\\d+\\W+or\\W+\"([^\"]+)\""); + + /// + /// Extracts the package description from the SDK list output. + /// + private static Regex PACKAGE_DESCRIPTION_REGEX = new Regex( + "^\\WDesc:\\W+(.+)"); + + /// + /// Extracts the install location from the SDK list output. + /// + private static Regex PACKAGE_INSTALL_LOCATION_REGEX = new Regex( + "^\\W+Install[^:]+:\\W+([^ ]+)"); + + // Path to the SDK manager tool. + private string toolPath; + // Path to the Android SDK. + private string sdkPath; + + /// + /// Initialize this instance. + /// + /// Path of the android tool. + /// Required to validate that a package is really installed. + public AndroidToolSdkManager(string toolPath, string sdkPath) { + this.toolPath = toolPath; + this.sdkPath = sdkPath; + } + + /// + /// Determines whether this is the legacy tool or the sdkmanager wrapper. + /// + public bool IsWrapper { + get { + // It's only possible to differentiate between the "android" package manager or + // sdkmanager wrapper by searching the output string for "deprecated" which is + // present in the wrapper. + var result = CommandLine.Run( + toolPath, "list sdk", + envVars: new Dictionary { { "USE_SDK_WRAPPER", "1" } }); + if (result.stdout.IndexOf("deprecated") >= 0) { + return true; + } + return false; + } + } + + /// + /// Parse "android list sdk -u -e -a" output. + /// + private AndroidSdkPackageCollection ParseAndroidListSdkOutput( + string androidListSdkOutput) { + var packages = new AndroidSdkPackageCollection(); + AndroidSdkPackage currentPackage = null; + foreach (string line in CommandLine.SplitLines(androidListSdkOutput)) { + // Check for the start of a new package entry. + if (line.StartsWith("---")) { + currentPackage = null; + continue; + } + Match match; + // If this is the start of a package description, add a package. + match = PACKAGE_ID_REGEX.Match(line); + if (match.Success) { + // TODO(smiles): Convert the legacy package name to a new package name. + currentPackage = new AndroidSdkPackage { LegacyName = match.Groups[1].Value }; + packages[currentPackage.Name].Add(currentPackage); + continue; + } + if (currentPackage == null) continue; + + // Add a package description. + match = PACKAGE_DESCRIPTION_REGEX.Match(line); + if (match.Success) { + currentPackage.Description = match.Groups[1].Value; + continue; + } + // Parse the install path and record whether the package is installed. + match = PACKAGE_INSTALL_LOCATION_REGEX.Match(line); + if (match.Success) { + currentPackage.Installed = File.Exists( + Path.Combine(Path.Combine(sdkPath, match.Groups[1].Value), + "source.properties")); + } + } + return packages; + } + + /// + /// Use the package manager to retrieve the set of installed and available packages. + /// + /// Called when the query is complete. + public void QueryPackages(Action complete) { + SdkManagerUtil.QueryPackages( + toolPath, "list sdk -u -e -a", + (CommandLine.Result result) => { + complete(result.exitCode == 0 ? + ParseAndroidListSdkOutput(result.stdout) : null); + }); + } + + /// + /// Install a set of packages. + /// + /// List of package versions to install / upgrade. + /// Called when installation is complete. + public void InstallPackages(HashSet packages, + Action complete) { + var packageNames = new List(); + foreach (var pkg in packages) packageNames.Add(pkg.LegacyName); + SdkManagerUtil.InstallPackages( + toolPath, String.Format( + "update sdk -a -u -t {0}", String.Join(",", packageNames.ToArray())), + packages, "Do you accept the license", "yes", "no", + new Regex("^--------"), complete); + } + } + + /// + /// Interacts with the Android SDK manager "sdkmanager". + /// + internal class SdkManager : IAndroidSdkManager { + /// Name of the SDK manager command line tool. + public const string TOOL_NAME = "sdkmanager"; + + // Marker followed by the list of installed packages. + private const string INSTALLED_PACKAGES_HEADER = "installed packages:"; + // Marker followed by the list of available packages. + private const string AVAILABLE_PACKAGES_HEADER = "available packages:"; + // Marker followed by the list of available package updates. + private const string AVAILABLE_UPDATES_HEADER = "available updates:"; + + /// Minimum version of the package that supports the --verbose flag. + public static long MINIMUM_VERSION_FOR_VERBOSE_OUTPUT = + AndroidSdkPackageNameVersion.ConvertVersionStringToInteger("26.0.2"); + + // Path to the SDK manager tool. + private string toolPath; + // Metadata for this package. + private AndroidSdkPackage toolsPackage; + + /// + /// Initialize this instance. + /// + /// Path of the android tool. + public SdkManager(string toolPath) { + this.toolPath = toolPath; + var toolsDir = Path.GetDirectoryName(Path.GetDirectoryName(toolPath)); + var sdkDir = Path.GetDirectoryName(toolsDir); + toolsPackage = AndroidSdkPackage.ReadFromSourceProperties( + sdkDir, toolsDir.Substring((sdkDir + Path.PathSeparator).Length)); + } + + /// + /// Read the metadata for the package that contains the package manager. + /// + public AndroidSdkPackage Package { get { return toolsPackage; } } + + /// + /// Parse "sdkmanager --list --verbose" output. + /// NOTE: The --verbose output format is only reported by sdkmanager 26.0.2 and above. + /// + private AndroidSdkPackageCollection ParseListVerboseOutput( + string sdkManagerListVerboseOutput) { + var packages = new AndroidSdkPackageCollection(); + // Whether we're parsing a set of packages. + bool parsingPackages = false; + // Whether we're parsing within the set of installed packages vs. available packages. + bool parsingInstalledPackages = false; + // Fields of the package being parsed. + AndroidSdkPackage currentPackage = null; + foreach (var rawLine in CommandLine.SplitLines(sdkManagerListVerboseOutput)) { + var line = rawLine.Trim(); + var lowerCaseLine = line.ToLower(); + if (lowerCaseLine == AVAILABLE_UPDATES_HEADER) { + parsingPackages = false; + continue; + } + bool installedPackagesLine = lowerCaseLine == INSTALLED_PACKAGES_HEADER; + bool availablePackagesLine = lowerCaseLine == AVAILABLE_PACKAGES_HEADER; + if (installedPackagesLine || availablePackagesLine) { + parsingPackages = true; + parsingInstalledPackages = installedPackagesLine; + continue; + } else if (line.StartsWith("---")) { + // Ignore section separators. + continue; + } else if (String.IsNullOrEmpty(line)) { + if (currentPackage != null && + !(String.IsNullOrEmpty(currentPackage.Name) || + String.IsNullOrEmpty(currentPackage.VersionString))) { + packages[currentPackage.Name].Add(currentPackage); + } + currentPackage = null; + continue; + } else if (!parsingPackages) { + continue; + } + // Fields of the package are indented. + bool indentedLine = rawLine.StartsWith(" "); + if (!indentedLine) { + // If this isn't an indented line it should be a package name. + if (currentPackage == null) { + currentPackage = new AndroidSdkPackage { + Name = line, + Installed = parsingInstalledPackages + }; + } + } else if (currentPackage != null) { + // Parse the package field. + var fieldSeparatorIndex = line.IndexOf(":"); + if (fieldSeparatorIndex >= 0) { + var fieldName = line.Substring(0, fieldSeparatorIndex).Trim().ToLower(); + var fieldValue = line.Substring(fieldSeparatorIndex + 1).Trim(); + if (fieldName == "description") { + currentPackage.Description = fieldValue; + } else if (fieldName == "version") { + currentPackage.VersionString = fieldValue; + } + } + } + } + return packages; + } + + /// + /// Parse "sdkmanager --list" output. + /// + /// Dictionary of packages bucketed by package name + private AndroidSdkPackageCollection ParseListOutput( + string sdkManagerListOutput) { + var packages = new AndroidSdkPackageCollection(); + // Whether we're parsing a set of packages. + bool parsingPackages = false; + // Whether we're parsing within the set of installed packages vs. available packages. + bool parsingInstalledPackages = false; + // Whether we're parsing the contents of the package table vs. the header. + bool inPackageTable = false; + foreach (var rawLine in CommandLine.SplitLines(sdkManagerListOutput)) { + var line = rawLine.Trim(); + var lowerCaseLine = line.ToLower(); + if (lowerCaseLine == AVAILABLE_UPDATES_HEADER) { + parsingPackages = false; + continue; + } + bool installedPackagesLine = lowerCaseLine == INSTALLED_PACKAGES_HEADER; + bool availablePackagesLine = lowerCaseLine == AVAILABLE_PACKAGES_HEADER; + if (installedPackagesLine || availablePackagesLine) { + parsingPackages = true; + parsingInstalledPackages = installedPackagesLine; + inPackageTable = false; + continue; + } + if (!parsingPackages) continue; + if (!inPackageTable) { + // If we've reached end of the table header, start parsing the set of packages. + if (line.StartsWith("----")) { + inPackageTable = true; + } + continue; + } + // Split into the fields package_name|version|description|location. + // Where "location" is an optional field that contains the install path. + var rawTokens = line.Split(new [] { '|' }); + if (rawTokens.Length < 3 || String.IsNullOrEmpty(line)) { + parsingPackages = false; + continue; + } + // Each field is surrounded by whitespace so trim the fields. + string[] tokens = new string[rawTokens.Length]; + for (int i = 0; i < rawTokens.Length; ++i) { + tokens[i] = rawTokens[i].Trim(); + } + var packageName = tokens[0]; + packages[packageName].Add(new AndroidSdkPackage { + Name = packageName, + Description = tokens[2], + VersionString = tokens[1], + Installed = parsingInstalledPackages + }); + } + return packages; + } + + /// + /// Use the package manager to retrieve the set of installed and available packages. + /// + /// Called when the query is complete. + public void QueryPackages(Action complete) { + bool useVerbose = Package != null && + Package.Version >= MINIMUM_VERSION_FOR_VERBOSE_OUTPUT; + SdkManagerUtil.QueryPackages( + toolPath, "--list" + (useVerbose ? " --verbose" : ""), + (CommandLine.Result result) => { + complete(result.exitCode == 0 ? + useVerbose ? ParseListVerboseOutput(result.stdout) : + ParseListOutput(result.stdout) : + null); + }); + } + + /// + /// Install a set of packages. + /// + /// List of package versions to install / upgrade. + /// Called when installation is complete. + public void InstallPackages(HashSet packages, + Action complete) { + var packagesString = AndroidSdkPackageNameVersion.ListToString(packages); + // TODO: Remove this dialog when the package manager provides feedback while + // downloading. + DialogWindow.Display( + "Missing Android SDK packages", + String.Format( + "Android SDK packages need to be installed:\n" + + "{0}\n" + + "\n" + + "The install process can be *slow* and does not provide any feedback " + + "which may lead you to think Unity has hung / crashed. Would you like " + + "to wait for these package to be installed?", + packagesString), + 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); + }); + } + } + + /// + /// Interacts with the available Android SDK package manager. + /// + internal class AndroidSdkManager { + /// + /// Find a tool in the Android SDK. + /// + /// 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. + /// String with the path to the tool if found, null otherwise. + private static string FindAndroidSdkTool(string toolName, string sdkPath = null) { + if (String.IsNullOrEmpty(sdkPath)) { + PlayServicesResolver.Log(String.Format( + "{0}\n" + + "Falling back to searching for the Android SDK tool {1} in the system path.", + PlayServicesSupport.AndroidSdkConfigurationError, toolName)); + } else { + var extensions = new List { CommandLine.GetExecutableExtension() }; + if (UnityEngine.RuntimePlatform.WindowsEditor == + UnityEngine.Application.platform) { + extensions.AddRange(new [] { ".bat", ".cmd" }); + } + foreach (var dir in new [] { "tools", Path.Combine("tools", "bin") }) { + foreach (var extension in extensions) { + var currentPath = Path.Combine(sdkPath, + Path.Combine(dir, toolName + extension)); + if (File.Exists(currentPath)) { + return currentPath; + } + } + } + } + var toolPath = CommandLine.FindExecutable(toolName); + return toolPath != null && File.Exists(toolPath) ? toolPath : null; + } + + /// + /// Log an error and complete a Create() operation. + /// + /// Action called with null. + private static void CreateFailed(Action complete) { + PlayServicesResolver.Log(String.Format( + "Unable to find either the {0} or {1} command line tool.\n\n" + + "It is not possible to query or install Android SDK packages.\n" + + "To resolve this issue, open the Android Package Manager" + + "and install the latest tools package.", + SdkManager.TOOL_NAME, AndroidToolSdkManager.TOOL_NAME)); + complete(null); + } + + /// + /// Create an instance of this class. + /// + /// If the package manager is out of date, the user is prompted to update it. + /// + /// Used to report a AndroidSdkManager instance if a SDK manager is + /// available, returns null otherwise. + public static void Create(string sdkPath, Action complete) { + // Search for the new package manager + var sdkManagerTool = FindAndroidSdkTool(SdkManager.TOOL_NAME, sdkPath: sdkPath); + if (sdkManagerTool != null) { + var sdkManager = new SdkManager(sdkManagerTool); + var sdkManagerPackage = sdkManager.Package; + if (sdkManagerPackage != null) { + // If the package manager is out of date, try updating it. + if (sdkManagerPackage.Version < SdkManager.MINIMUM_VERSION_FOR_VERBOSE_OUTPUT) { + sdkManager.QueryPackages( + (AndroidSdkPackageCollection packages) => { + sdkManagerPackage = packages.GetMostRecentAvailablePackage( + sdkManagerPackage.Name); + if (sdkManagerPackage != null) { + sdkManager.InstallPackages( + new HashSet( + new [] { sdkManagerPackage }), + (bool success) => { + complete(success ? sdkManager : null); + }); + } else { + CreateFailed(complete); + } + }); + } else { + complete(sdkManager); + } + return; + } + } + + // Search for the legacy package manager. + var androidTool = FindAndroidSdkTool("android", sdkPath: sdkPath); + if (androidTool != null) { + var sdkManager = new AndroidToolSdkManager(androidTool, sdkPath); + if (!sdkManager.IsWrapper) { + complete(sdkManager); + return; + } + } + CreateFailed(complete); + } + } +} diff --git a/source/AndroidResolver/src/AndroidXmlDependencies.cs b/source/AndroidResolver/src/AndroidXmlDependencies.cs new file mode 100644 index 00000000..5af00803 --- /dev/null +++ b/source/AndroidResolver/src/AndroidXmlDependencies.cs @@ -0,0 +1,176 @@ +// +// Copyright (C) 2017 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.Xml; + using Google; + using Google.JarResolver; + using UnityEditor; + + /// + /// Parses XML declared dependencies required by the Android Resolver Unity plugin. + /// + internal class AndroidXmlDependencies : XmlDependencies { + internal PlayServicesSupport svcSupport = null; + + public AndroidXmlDependencies() { + dependencyType = "Android dependencies"; + } + + /// + /// Read XML declared dependencies. + /// + /// File to read. + /// Logger instance to log with. + /// + /// Parses dependencies in the form: + /// + /// + /// + /// + /// + /// androidPackageManagerPackageId + /// + /// + /// uriToRepositoryToSearchForPackage + /// + /// + /// + /// + protected override bool Read(string filename, Logger logger) { + List androidSdkPackageIds = null; + 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), + level: LogLevel.Verbose); + + if (!XmlUtilities.ParseXmlTextFileElements( + filename, logger, + (reader, elementName, isStart, parentElementName, elementNameStack) => { + if (elementName == "dependencies" && parentElementName == "") { + return true; + } else if (elementName == "androidPackages" && + (parentElementName == "dependencies" || + parentElementName == "")) { + return true; + } else if (elementName == "androidPackage" && + parentElementName == "androidPackages") { + if (isStart) { + androidSdkPackageIds = new List(); + 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 && specComponents.Length != 4) { + logger.Log( + String.Format( + "Ignoring invalid package specification '{0}' " + + "while reading {1}:{2}\n", + spec, filename, reader.LineNumber), + level: LogLevel.Warning); + return false; + } + 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, classifier: classifier, + packageIds: androidSdkPackageIds.ToArray(), + repositories: repositories.ToArray(), + createdBy: String.Format("{0}:{1}", filename, + reader.LineNumber)); + } + } else if (elementName == "androidSdkPackageIds" && + parentElementName == "androidPackage") { + return true; + } else if (elementName == "androidSdkPackageId" && + parentElementName == "androidSdkPackageIds") { + // Parse package manager ID associated with this package. + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + androidSdkPackageIds.Add(reader.ReadContentAsString()); + } + return true; + } else if (elementName == "repositories" && + parentElementName == "androidPackage") { + return true; + } else if (elementName == "repositories" && + parentElementName == "androidPackages") { + if (isStart) { + repositories = new List(); + } else { + foreach (var repo in repositories) { + PlayServicesSupport.AdditionalRepositoryPaths.Add( + new KeyValuePair( + repo, String.Format("{0}:{1}", filename, + reader.LineNumber))); + } + } + return true; + } else if (elementName == "repository" && + parentElementName == "repositories") { + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + repositories.Add(reader.ReadContentAsString()); + } + return true; + } + // Ignore unknown tags so that different configurations can be stored in the + // same file. + return true; + })) { + return false; + } + return true; + } + + /// + /// Find and read all XML declared dependencies. + /// + /// Logger to log with. + public override bool ReadAll(Logger logger) { + const string XML_DEPENDENCIES_INSTANCE = "InternalXmlDependencies"; + if (PlayServicesSupport.instances.TryGetValue(XML_DEPENDENCIES_INSTANCE, + out svcSupport)) { + svcSupport.ClearDependencies(); + } else { + svcSupport = PlayServicesSupport.CreateInstance( + XML_DEPENDENCIES_INSTANCE, PlayServicesResolver.AndroidSdkRoot, + "ProjectSettings", logMessageWithLevel: PlayServicesResolver.LogDelegate); + } + return base.ReadAll(logger); + } + } +} diff --git a/source/PlayServicesResolver/src/CommandLine.cs b/source/AndroidResolver/src/CommandLine.cs similarity index 65% rename from source/PlayServicesResolver/src/CommandLine.cs rename to source/AndroidResolver/src/CommandLine.cs index 496cce4a..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; @@ -26,6 +27,8 @@ namespace GooglePlayServices using UnityEditor; #endif // UNITY_EDITOR + using Google; + public static class CommandLine { /// @@ -39,6 +42,13 @@ public class Result public string stderr; /// Exit code returned by the tool when execution is complete. public int exitCode; + /// String that can be used in an error message. + /// This contains: + /// * The command executed. + /// * Standard output string. + /// * Standard error string. + /// * Exit code. + public string message; }; /// @@ -106,26 +116,32 @@ public static StreamData Empty /// /// Asynchronously execute a command line tool, calling the specified delegate on /// completion. + /// NOTE: In batch mode this will be executed synchronously. /// /// Tool to execute. /// String to pass to the tools' command line. - /// Called when the tool completes. + /// Called when the tool completes. This is always + /// called from the main thread. /// Directory to execute the tool from. /// Additional environment variables to set for the command. /// Allows a caller to provide interactive input and also handle /// both output and error streams from a single delegate. public static void RunAsync( - string toolPath, string arguments, CompletionHandler completionDelegate, - string workingDirectory = null, - Dictionary envVars = null, - IOHandler ioHandler = null) - { - Thread thread = new Thread(new ThreadStart(() => { - Result result = Run(toolPath, arguments, workingDirectory, envVars: envVars, - ioHandler: ioHandler); - completionDelegate(result); - })); - thread.Start(); + string toolPath, string arguments, CompletionHandler completionDelegate, + string workingDirectory = null, + Dictionary envVars = null, + IOHandler ioHandler = null) { + Action action = () => { + Result result = Run(toolPath, arguments, workingDirectory, envVars: envVars, + ioHandler: ioHandler); + RunOnMainThread.Run(() => { completionDelegate(result);}); + }; + if (ExecutionEnvironment.InBatchMode) { + RunOnMainThread.Run(action); + } else { + Thread thread = new Thread(new ThreadStart(action)); + thread.Start(); + } } /// @@ -205,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)); } } @@ -359,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; @@ -381,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) { @@ -491,27 +506,132 @@ public void AggregateLine(Process process, StreamWriter stdin, StreamData data) /// possible to execute the tool. public static Result Run(string toolPath, string arguments, string workingDirectory = null, Dictionary envVars = null, - IOHandler ioHandler = null) - { - System.Text.Encoding inputEncoding = Console.InputEncoding; - System.Text.Encoding outputEncoding = Console.OutputEncoding; - Console.InputEncoding = System.Text.Encoding.UTF8; - Console.OutputEncoding = System.Text.Encoding.UTF8; + IOHandler ioHandler = null) { + return RunViaShell(toolPath, arguments, workingDirectory: workingDirectory, + envVars: envVars, ioHandler: ioHandler, useShellExecution: false); + } + + /// + /// Whether the console configuration warning has been displayed. + /// + private static bool displayedConsoleConfigurationWarning = false; + + /// + /// Execute a command line tool. + /// + /// Tool to execute. On Windows, if the path to this tool contains + /// single quotes (apostrophes) the tool will be executed via the shell. + /// String to pass to the tools' command line. + /// Directory to execute the tool from. + /// Additional environment variables to set for the command. + /// Allows a caller to provide interactive input and also handle + /// both output and error streams from a single delegate. NOTE: This is ignored if + /// shell execution is enabled. + /// Execute the command via the shell. This disables + /// I/O redirection and causes a window to be popped up when the command is executed. + /// This uses file redirection to retrieve the standard output stream. + /// + /// Enables stdout and stderr redirection when + /// 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, 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 { + 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) { + 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. + // The following checks for this condition and forces shell execution of tools in these + // paths which works fine as the shell tool should be in the system PATH. + if (UnityEngine.RuntimePlatform.WindowsEditor == UnityEngine.Application.platform && + 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; + string stderrFileName = null; + if (useShellExecution && stdoutRedirectionInShellMode) { + stdoutFileName = Path.GetTempFileName(); + stderrFileName = Path.GetTempFileName(); + string shellCmd ; + string shellArgPrefix; + string shellArgPostfix; + string escapedToolPath = toolPath; + string additionalCommands = ""; + if (UnityEngine.RuntimePlatform.WindowsEditor == UnityEngine.Application.platform) { + shellCmd = "cmd.exe"; + shellArgPrefix = "/c \""; + shellArgPostfix = "\""; + } else { + shellCmd = "bash"; + 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}\" {3} 1> {4} 2> {5}{6}", shellArgPrefix, + additionalCommands, escapedToolPath, arguments, stdoutFileName, + stderrFileName, shellArgPostfix); + toolPath = shellCmd; + } Process process = new Process(); - process.StartInfo.UseShellExecute = false; + process.StartInfo.UseShellExecute = useShellExecution; process.StartInfo.Arguments = arguments; - process.StartInfo.CreateNoWindow = true; - process.StartInfo.RedirectStandardOutput = true; - process.StartInfo.RedirectStandardError = true; - if (envVars != null) - { - foreach (var env in envVars) - { - process.StartInfo.EnvironmentVariables[env.Key] = env.Value; + if (useShellExecution) { + process.StartInfo.CreateNoWindow = false; + process.StartInfo.RedirectStandardOutput = false; + process.StartInfo.RedirectStandardError = false; + } else { + process.StartInfo.CreateNoWindow = true; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + if (envVars != null) { + foreach (var env in envVars) { + process.StartInfo.EnvironmentVariables[env.Key] = env.Value; + } } } - process.StartInfo.RedirectStandardInput = (ioHandler != null); + process.StartInfo.RedirectStandardInput = !useShellExecution && (ioHandler != null); process.StartInfo.FileName = toolPath; process.StartInfo.WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory; @@ -521,32 +641,59 @@ public static Result Run(string toolPath, string arguments, string workingDirect // handle before output data is sent to it. if (ioHandler != null) ioHandler(process, process.StandardInput, StreamData.Empty); - AutoResetEvent complete = new AutoResetEvent(false); - List[] stdouterr = new List[] { new List(), - new List() }; - // Read raw output from the process. - AsyncStreamReader[] readers = AsyncStreamReader.CreateFromStreams( - new Stream[] { process.StandardOutput.BaseStream, - process.StandardError.BaseStream }, 1); - new AsyncStreamReaderMultiplexer( - readers, - (StreamData data) => { - stdouterr[data.handle].Add(data.text); - if (ioHandler != null) ioHandler(process, process.StandardInput, data); - }, - complete: () => { complete.Set(); }); - foreach (AsyncStreamReader reader in readers) reader.Start(); - - process.WaitForExit(); - // Wait for the reading threads to complete. - complete.WaitOne(); + List[] stdouterr = new List[] { + new List(), new List() }; + + if (useShellExecution) { + process.WaitForExit(); + if (stdoutRedirectionInShellMode) { + stdouterr[0].Add(File.ReadAllText(stdoutFileName)); + stdouterr[1].Add(File.ReadAllText(stderrFileName)); + File.Delete(stdoutFileName); + File.Delete(stderrFileName); + } + } else { + AutoResetEvent complete = new AutoResetEvent(false); + // Read raw output from the process. + AsyncStreamReader[] readers = AsyncStreamReader.CreateFromStreams( + new Stream[] { process.StandardOutput.BaseStream, + process.StandardError.BaseStream }, 1); + new AsyncStreamReaderMultiplexer( + readers, + (StreamData data) => { + stdouterr[data.handle].Add(data.text); + if (ioHandler != null) ioHandler(process, process.StandardInput, data); + }, + complete: () => { complete.Set(); }); + foreach (AsyncStreamReader reader in readers) reader.Start(); + + process.WaitForExit(); + // Wait for the reading threads to complete. + complete.WaitOne(); + } Result result = new Result(); result.stdout = String.Join("", stdouterr[0].ToArray()); result.stderr = String.Join("", stdouterr[1].ToArray()); result.exitCode = process.ExitCode; - Console.InputEncoding = inputEncoding; - Console.OutputEncoding = outputEncoding; + result.message = FormatResultMessage(toolPath, arguments, result.stdout, + result.stderr, result.exitCode); + 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; } @@ -559,6 +706,28 @@ public static string[] SplitLines(string multilineString) return Regex.Split(multilineString, "\r\n|\r|\n"); } + /// + /// Format a command execution error message. + /// + /// Tool executed. + /// Arguments used to execute the tool. + /// Standard output stream from tool execution. + /// Standard error stream from tool execution. + /// Result of the tool. + private static string FormatResultMessage(string toolPath, string arguments, + string stdout, string stderr, + int exitCode) { + return String.Format( + "{0} '{1} {2}'\n" + + "stdout:\n" + + "{3}\n" + + "stderr:\n" + + "{4}\n" + + "exit code: {5}\n", + exitCode == 0 ? "Successfully executed" : "Failed to run", + toolPath, arguments, stdout, stderr, exitCode); + } + #if UNITY_EDITOR /// /// Get an executable extension. diff --git a/source/PlayServicesResolver/src/CommandLineDialog.cs b/source/AndroidResolver/src/CommandLineDialog.cs similarity index 65% rename from source/PlayServicesResolver/src/CommandLineDialog.cs rename to source/AndroidResolver/src/CommandLineDialog.cs index 11f0287f..570d45cb 100644 --- a/source/PlayServicesResolver/src/CommandLineDialog.cs +++ b/source/AndroidResolver/src/CommandLineDialog.cs @@ -24,7 +24,9 @@ namespace GooglePlayServices using UnityEditor; using UnityEngine; - public class CommandLineDialog : TextAreaDialog + using Google; + + internal class CommandLineDialog : TextAreaDialog { /// /// Forwards the output of the currently executing command to a CommandLineDialog window. @@ -43,6 +45,8 @@ public class ProgressReporter : CommandLine.LineReader private volatile int linesReported; // Command line tool result, set when command line execution is complete. private volatile CommandLine.Result result = null; + // Logger for messages. + private Google.Logger logger = null; /// /// Event called on the main / UI thread when the outstanding command line tool @@ -53,12 +57,13 @@ public class ProgressReporter : CommandLine.LineReader /// /// Construct a new reporter. /// - public ProgressReporter(CommandLine.IOHandler handler = null) - { + /// Logger to log command output. + public ProgressReporter(Google.Logger logger) { textQueue = System.Collections.Queue.Synchronized(new System.Collections.Queue()); maxProgressLines = 0; linesReported = 0; LineHandler += CommandLineIOHandler; + this.logger = logger; Complete = null; } @@ -70,7 +75,7 @@ private int CountLines(string str) /// /// Called from RunCommandLine() tool to report the output of the currently - /// executing commmand. + /// executing command. /// /// Executing process. /// Standard input stream. @@ -82,7 +87,12 @@ private void CommandLineIOHandler(Process process, StreamWriter stdin, // Count lines in stdout. if (data.handle == 0) linesReported += CountLines(data.text); // Enqueue data for the text view. - textQueue.Enqueue(System.Text.Encoding.UTF8.GetString(data.data)); + var newLines = System.Text.Encoding.UTF8.GetString(data.data); + textQueue.Enqueue(newLines); + // Write to the logger. + foreach (var line in CommandLine.SplitLines(newLines)) { + logger.Log(line, level: LogLevel.Verbose); + } } /// @@ -90,7 +100,22 @@ private void CommandLineIOHandler(Process process, StreamWriter stdin, /// public void CommandLineToolCompletion(CommandLine.Result result) { + logger.Log( + String.Format("Command completed: {0}", result.message), + level: LogLevel.Verbose); this.result = result; + SignalComplete(); + } + + /// + /// Signal the completion event handler. + /// This method *must* be called from the main thread. + /// + private void SignalComplete() { + if (Complete != null) { + Complete(result); + Complete = null; + } } /// @@ -123,10 +148,6 @@ public void Update(CommandLineDialog window) bodyText = bodyTextHead + bodyText.Substring(carriageReturn + 1); } window.bodyText = bodyText; - if (window.autoScrollToBottom) - { - window.scrollPosition.y = Mathf.Infinity; - } window.Repaint(); } if (maxProgressLines > 0) @@ -136,11 +157,7 @@ public void Update(CommandLineDialog window) if (result != null) { window.progressTitle = ""; - if (Complete != null) - { - Complete(result); - Complete = null; - } + SignalComplete(); } } } @@ -148,7 +165,12 @@ public void Update(CommandLineDialog window) public volatile float progress; public string progressTitle; public string progressSummary; - public volatile bool autoScrollToBottom; + public Google.Logger logger = new Google.Logger(); + + /// + /// Whether a command is currently being executed. + /// + public bool RunningCommand { protected set; get; } /// /// Event delegate called from the Update() method of the window. @@ -157,20 +179,46 @@ public void Update(CommandLineDialog window) public event UpdateDelegate UpdateEvent; - private bool progressBarVisible; - /// /// Create a dialog box which can display command line output. /// /// Reference to the new window. public static CommandLineDialog CreateCommandLineDialog(string title) { - CommandLineDialog window = (CommandLineDialog)EditorWindow.GetWindow( - typeof(CommandLineDialog), true, title); + // In batch mode we simply create the class without instancing the visible window. + CommandLineDialog window = ExecutionEnvironment.InBatchMode ? + new CommandLineDialog() : (CommandLineDialog)EditorWindow.GetWindow( + typeof(CommandLineDialog), true, title); window.Initialize(); return window; } + /// + /// Alternative the Show() method that does not crash in batch mode. + /// + public new void Show() { + Show(false); + } + + /// + /// Alternative Show() method that does not crash in batch mode. + /// + /// Display the window now. + public new void Show(bool immediateDisplay) { + if (!ExecutionEnvironment.InBatchMode) { + base.Show(immediateDisplay); + } + } + + /// + /// Alternative Close() method that does not crash in batch mode. + /// + public new void Close() { + if (!ExecutionEnvironment.InBatchMode) { + base.Close(); + } + } + /// /// Initialize all members of the window. /// @@ -181,8 +229,42 @@ public override void Initialize() progressTitle = ""; progressSummary = ""; UpdateEvent = null; - progressBarVisible = false; autoScrollToBottom = false; + logRedirector.ShouldLogDelegate = () => { return !RunningCommand; }; + } + + /// + /// Set the progress bar status. + /// + /// Text to display before the progress bar. + /// Progress bar value 0..1. + /// Text to display in the progress bar. + public void SetProgress(string title, float value, string summary) { + progressTitle = title; + progress = value; + progressSummary = summary; + Repaint(); + } + + // Draw the GUI with an optional status bar. + protected override void OnGUI() { + summaryTextDisplay = true; + if (!String.IsNullOrEmpty(progressTitle)) { + summaryTextDisplay = false; + 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, progressValue, + String.IsNullOrEmpty(progressSummary) ? + String.Format("{0}%... ", (int)(progressValue * 100.0f)) : progressSummary); + EditorGUILayout.EndVertical(); + EditorGUILayout.Space(); + EditorGUILayout.EndVertical(); + } + base.OnGUI(); } /// @@ -204,7 +286,8 @@ public void RunAsync( string workingDirectory = null, Dictionary envVars = null, CommandLine.IOHandler ioHandler = null, int maxProgressLines = 0) { - CommandLineDialog.ProgressReporter reporter = new CommandLineDialog.ProgressReporter(); + CommandLineDialog.ProgressReporter reporter = + new CommandLineDialog.ProgressReporter(logger); reporter.maxProgressLines = maxProgressLines; // Call the reporter from the UI thread from this window. UpdateEvent += reporter.Update; @@ -213,31 +296,30 @@ public void RunAsync( // Connect the caller's IoHandler delegate to the reporter. reporter.DataHandler += ioHandler; // Disconnect the reporter when the command completes. - CommandLine.CompletionHandler reporterUpdateDisable = - (CommandLine.Result unusedResult) => { this.UpdateEvent -= reporter.Update; }; + CommandLine.CompletionHandler reporterUpdateDisable = (unusedResult) => { + RunningCommand = false; + this.UpdateEvent -= reporter.Update; + }; reporter.Complete += reporterUpdateDisable; + logger.Log(String.Format( + "Executing command: {0} {1}", toolPath, arguments), level: LogLevel.Verbose); + RunningCommand = true; CommandLine.RunAsync(toolPath, arguments, reporter.CommandLineToolCompletion, workingDirectory: workingDirectory, envVars: envVars, ioHandler: reporter.AggregateLine); } /// - /// Call the update event from the UI thread, optionally display / hide the progress bar. + /// Call the update event from the UI thread. /// protected virtual void Update() { if (UpdateEvent != null) UpdateEvent(this); - if (progressTitle != "") - { - progressBarVisible = true; - EditorUtility.DisplayProgressBar(progressTitle, progressSummary, - progress); - } - else if (progressBarVisible) - { - progressBarVisible = false; - EditorUtility.ClearProgressBar(); - } + } + + // Hide the progress bar if the window is closed. + protected override void OnDestroy() { + base.OnDestroy(); } } } 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/AndroidResolver/src/JavaUtilities.cs b/source/AndroidResolver/src/JavaUtilities.cs new file mode 100644 index 00000000..54b87beb --- /dev/null +++ b/source/AndroidResolver/src/JavaUtilities.cs @@ -0,0 +1,242 @@ +// +// Copyright (C) 2017 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.Globalization; + using System.IO; + using System.Text.RegularExpressions; + using UnityEditor; + + using Google; + using Google.JarResolver; + + /// + /// Utilities to determine Java tool installation and validate the Java installation for the + /// project's build settings. + /// + internal static class JavaUtilities { + + /// + /// Exception thrown if a Java tool isn't found. + /// + internal class ToolNotFoundException : ApplicationException { + public ToolNotFoundException(string message) : base(message) {} + } + + /// + /// Environment variable used to specify the Java distribution directory. + /// + internal const string JAVA_HOME = "JAVA_HOME"; + + /// + /// Minimum JDK version required to build with recently released Android libraries. + /// + private static Version MinimumJdkVersion = new Version("1.8"); + + /// + /// Find the JDK path (JAVA_HOME) either configured in the Unity editor or via the JAVA_HOME + /// environment variable. + /// + internal static string JavaHome { + get { + 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")) { + var androidPlayerDir = PlayServicesResolver.AndroidPlaybackEngineDirectory; + if (!String.IsNullOrEmpty(androidPlayerDir)) { + var platformDir = UnityEngine.Application.platform.ToString().Replace( + "Editor", "").Replace("OSX", "MacOS"); + var openJdkDir = Path.Combine(Path.Combine(Path.Combine( + androidPlayerDir, "Tools"), "OpenJDK"), platformDir); + 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); + } + return javaHome; + } + } + + /// + /// Get the path to the "jar" binary. + /// + /// Path to the "jar" binary if successful, throws ToolNotFoundException + /// otherwise. + internal static string JarBinaryPath { + get { return FindJavaTool("jar"); } + } + + /// + /// Get the path to the "java" binary. + /// + /// Path to the "java" binary if successful, throws ToolNotFoundException + /// otherwise. + internal static string JavaBinaryPath { + get { return FindJavaTool("java"); } + } + + static JavaUtilities() { + // TODO(smiles): Register a check of the JDK version vs. the current build settings. + } + + /// + /// Construct a path to a binary in the Java distribution. + /// + /// Name of the tool within the Java binary directory. + /// Path to the tool if it exists, null otherwise. + private static string JavaHomeBinaryPath(string javaTool) { + if (!String.IsNullOrEmpty(JavaHome)) { + string toolPath = Path.Combine( + JavaHome, Path.Combine("bin", javaTool + CommandLine.GetExecutableExtension())); + if (File.Exists(toolPath)) { + return toolPath; + } + } + return null; + } + + /// + /// Find a Java tool. + /// + /// 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) + { + var javaHome = JavaHome; + string toolPath = null; + if (!String.IsNullOrEmpty(javaHome)) { + toolPath = JavaHomeBinaryPath(javaTool); + if (String.IsNullOrEmpty(toolPath)) { + 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), + DialogWindow.Option.Selected0, "OK"); + throw new ToolNotFoundException( + String.Format("{0} not found, {1} references incomplete Java distribution.", + javaTool, javaHome)); + + } + } else { + toolPath = CommandLine.FindExecutable(javaTool); + if (!File.Exists(toolPath)) { + 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), + DialogWindow.Option.Selected0, "OK"); + throw new ToolNotFoundException(javaTool + " not found."); + } + } + return toolPath; + } + + /// + /// Log Jdk version parsing failed warning. + /// + /// Path to the java tool. + /// Summary of the executed command line. + private static void LogJdkVersionFailedWarning(string javaPath, string commandLineSummary) { + PlayServicesResolver.Log( + String.Format( + "Failed to get Java version when running {0}\n" + + "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); + } + + /// + /// Determine whether the user's JDK is sufficient for the Android SDK and recently + /// released libraries. + /// + internal static void CheckJdkForApiLevel() { + // Get JAVA_HOME path from the editor settings. + string javaPath = null; + try { + javaPath = JavaBinaryPath; + } catch (ToolNotFoundException) { + return; + } + var result = CommandLine.Run(javaPath, "-version", Directory.GetCurrentDirectory(), + envVars: new Dictionary { + { JAVA_HOME, JavaHome } + }); + if (result.exitCode != 0) { + LogJdkVersionFailedWarning(javaPath, result.message); + return; + } + Version foundVersion = null; + // The version string is can be reported via stderr or stdout so scrape the + // concatenated message string. + string pattern = "^(?java||openjdk) version \"(?[^\"]*)\".*$"; + + Match match = Regex.Match(result.message, pattern, RegexOptions.Multiline); + if (match.Success) { + String versionString = match.Groups["version"].Value; + // Version requires a Max and Min version, so if there is only one version, + // add a 0 minor version. + if (!versionString.Contains(".")) { + versionString += ".0"; + } + foundVersion = new Version(Regex.Replace(versionString, "[^0-9\\.]", "")); + } + if (foundVersion == null) { + LogJdkVersionFailedWarning(javaPath, result.message); + return; + } + // 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" + + "Please install JDK version {1} or newer and configure Unity " + + "to use the new JDK installation in the " + + "'Unity Preferences > External Tools' menu.\n", + foundVersion, MinimumJdkVersion), + level: LogLevel.Error); + } + } + } + +} diff --git a/source/AndroidResolver/src/LocalMavenRepository.cs b/source/AndroidResolver/src/LocalMavenRepository.cs new file mode 100644 index 00000000..b4467ccf --- /dev/null +++ b/source/AndroidResolver/src/LocalMavenRepository.cs @@ -0,0 +1,226 @@ +// +// 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.Xml; + + using Google; + using Google.JarResolver; + + /// + /// Finds and operates on Maven repositories embedded in the Unity project. + /// + internal class LocalMavenRepository { + + /// + /// Find paths to repositories that are included in the project. + /// + /// Dependencies to search for local repositories. + /// Set of repository paths in the project. + public static HashSet FindLocalRepos(ICollection dependencies) { + // Find all repositories embedded in the project. + var repos = new HashSet(); + 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; + } + + /// + /// Find all .aar and .srcaar files under a directory. + /// + /// Directory to recursively search. + /// A list of found aar and srcaar files. + public static List FindAars(string directory) { + var foundFiles = new List(); + 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; + } + + /// + /// Find all .aar and .srcaar files in the project's repositories. + /// + /// Dependencies to search for local repositories. + /// A list of found aar and srcaar files. + public static List FindAarsInLocalRepos(ICollection dependencies) { + var libraries = new List(); + foreach (var repo in FindLocalRepos(dependencies)) { + libraries.AddRange(FindAars(repo)); + } + return libraries; + } + + /// + /// Get a path without a filename extension. + /// + /// Path to a file. + /// Path (including directory) without a filename extension. + internal static string PathWithoutExtension(string path) { + return Path.Combine(Path.GetDirectoryName(path), + Path.GetFileNameWithoutExtension(path)); + } + + /// + /// Search for the POM file associated with the specified maven artifact and patch the + /// packaging reference if the POM doesn't reference the artifact. + /// 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, 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"; + // 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(); + try { + using (var stream = new StreamReader(pomFilename)) { + pom.Load(stream); + } + } catch (Exception ex) { + PlayServicesResolver.Log( + String.Format("Unable to read maven POM {0} for {1} ({2}). " + failureImpact, + pom, artifactFilename, ex), level: LogLevel.Error); + return false; + } + bool updatedPackaging = false; + XmlNodeList packagingNode = pom.GetElementsByTagName("packaging"); + foreach (XmlNode node in packagingNode) { + if (node.InnerText != artifactPackaging) { + PlayServicesResolver.Log(String.Format( + "Replacing packaging of maven POM {0} {1} --> {2}", + pomFilename, node.InnerText, artifactPackaging), level: LogLevel.Verbose); + node.InnerText = artifactPackaging; + 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 = + XmlWriter.Create(pomFilename, + new XmlWriterSettings { + Indent = true, + IndentChars = " ", + NewLineChars = "\n", + NewLineHandling = NewLineHandling.Replace + })) { + pom.Save(xmlWriter); + } + } catch (Exception ex) { + PlayServicesResolver.Log( + String.Format("Unable to write patch maven POM {0} for {1} with " + + "packaging {2} ({3}). " + failureImpact, + pom, artifactFilename, artifactPackaging, ex)); + return false; + } + } + return true; + } + + /// + /// Patch all POM files in the local repository with PatchPomFile(). + /// If a srcaar and an aar are present in the same directory the POM is patched with a + /// reference to the aar. + /// + /// true if successful, false otherwise. + public static bool PatchPomFilesInLocalRepos(ICollection dependencies) { + // 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)) { + extensions = new HashSet(); + extensionsByBasenames[pathWithoutExtension] = extensions; + } + extensions.Add(Path.GetExtension(filename)); + } + bool successful = true; + var packagingPriority = new [] { ".aar", ".srcaar" }; + foreach (var kv in extensionsByBasenames) { + string filePackagingToUse = ""; + foreach (var packaging in packagingPriority) { + bool foundFile = false; + foreach (var filenamePackaging in kv.Value) { + filePackagingToUse = filenamePackaging; + if (filenamePackaging.ToLower() == packaging) { + foundFile = true; + break; + } + } + if (foundFile) break; + } + var artifect = kv.Key + filePackagingToUse; + successful &= PatchPomFile(artifect, artifect); + } + return successful; + } + } + +} diff --git a/source/AndroidResolver/src/PlayServicesPreBuild.cs b/source/AndroidResolver/src/PlayServicesPreBuild.cs new file mode 100644 index 00000000..21c28cbd --- /dev/null +++ b/source/AndroidResolver/src/PlayServicesPreBuild.cs @@ -0,0 +1,44 @@ +namespace GooglePlayServices { + using UnityEditor; + using UnityEditor.Callbacks; + using UnityEngine; + internal class PlayServicesPreBuild { + // Flag to ensure that we only warn once per build. + private static bool HasWarned; + + /// + /// Add a pre-build hook to warn the user if they have disabled + /// the Android auto-resolution functionality and they are building. + /// + [PostProcessScene(0)] + private static void WarnIfAutoResolveDisabled() { + if (HasWarned || + EditorApplication.isPlaying || + EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android) + return; + + if (SettingsDialog.AutoResolutionDisabledWarning && + !(SettingsDialog.EnableAutoResolution || SettingsDialog.AutoResolveOnBuild)) { + Debug.LogWarning("Warning: Auto-resolution of Android dependencies is disabled! " + + "Ensure you have run the resolver manually." + + "\n\nWith auto-resolution of Android dependencies disabled you " + + "must manually resolve dependencies using the " + + "\"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 > External Dependency Manager > Android Resolver > " + + "Settings\" and check \"Enable Auto-resolution\""); + } + + HasWarned = true; + } + + /// Once the build is complete, call back to this method and reset the + /// Generated flag to set up for the next build. + [PostProcessBuild(0)] + private static void BuildComplete(BuildTarget target, string pathToBuiltProject) { + HasWarned = false; + } + } +} diff --git a/source/AndroidResolver/src/PlayServicesResolver.cs b/source/AndroidResolver/src/PlayServicesResolver.cs new file mode 100644 index 00000000..e4f7d40e --- /dev/null +++ b/source/AndroidResolver/src/PlayServicesResolver.cs @@ -0,0 +1,2919 @@ +// +// 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 System.Linq; + using System.Text.RegularExpressions; + using System.Threading; + using System.Xml; + using Google; + 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 + /// that copies over the Google play services .aar files that + /// plugins have declared as dependencies. If the Unity version is less than + /// 5, aar files are not supported so this class 'explodes' the aar file into + /// a plugin directory. Once the version of Unity is upgraded, the exploded + /// files are removed in favor of the .aar files. + /// + [InitializeOnLoad] + public class PlayServicesResolver : AssetPostprocessor { + /// + /// Saves the current state of dependencies in the project and allows the caller to + /// compare the current state vs. the previous state of dependencies in the project. + /// + internal class DependencyState { + /// + /// Set of dependencies and the expected files in the project. + /// + private static string DEPENDENCY_STATE_FILE = Path.Combine( + "ProjectSettings", "AndroidResolverDependencies.xml"); + + /// + /// Set of Android packages (AARs / JARs) referenced by this DependencyState. + /// These are in the Maven style format "group:artifact:version". + /// + public HashSet Packages { get; internal set; } + + /// + /// Set of files referenced by this 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. + /// + /// DependencyState instance with data derived from the current + /// project. + public static DependencyState GetState() { + return new DependencyState { + Packages = new HashSet(PlayServicesSupport.GetAllDependencies().Keys), + Files = new HashSet(PlayServicesResolver.FindLabeledAssets()), + Settings = PlayServicesResolver.GetResolutionSettings(), + }; + } + + /// + /// Sort a string hashset. + /// + /// Set to sort and return via an enumerable. + private static IEnumerable SortSet(IEnumerable setToSort) { + var sorted = new SortedDictionary(); + foreach (var value in setToSort) sorted[value] = true; + return sorted.Keys; + } + + /// + /// Write this object to DEPENDENCY_STATE_FILE. + /// + 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, + }) { + writer.WriteStartElement("dependencies"); + writer.WriteStartElement("packages"); + foreach (var dependencyKey in SortSet(Packages)) { + writer.WriteStartElement("package"); + writer.WriteValue(dependencyKey); + writer.WriteEndElement(); + } + writer.WriteEndElement(); + writer.WriteStartElement("files"); + foreach (var assetPath in SortSet(Files)) { + writer.WriteStartElement("file"); + 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(); + writer.WriteEndElement(); + writer.Flush(); + writer.Close(); + } + + } catch (Exception e) { + Log(String.Format( + "Unable to update dependency file {0} ({1})\n" + + "If auto-resolution is enabled, it is likely to be retriggered " + + "when any operation triggers resolution.", DEPENDENCY_STATE_FILE, e), + level: LogLevel.Warning); + } + } + + /// + /// Read the state from DEPENDENCY_STATE_FILE. + /// + /// DependencyState instance read from DEPENDENCY_STATE_FILE. null is + /// returned if the file isn't found. + /// + /// This parses files in the following format: + /// + /// + /// group:artifact:version + /// ... + /// + /// + /// package_filename + /// ... + /// + /// + 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" || + elementName == "settings") && + parentElementName == "dependencies") { + return true; + } else if (elementName == "package" && + parentElementName == "packages") { + if (isStart && reader.Read() && + reader.NodeType == XmlNodeType.Text) { + packages.Add(reader.ReadContentAsString()); + } + return true; + } else if (elementName == "file" && parentElementName == "files") { + if (isStart && reader.Read() && + reader.NodeType == XmlNodeType.Text) { + 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 true; + })) { + return null; + } + return new DependencyState() { + Packages = packages, + Files = files, + Settings = settings + }; + } + + /// + /// 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 state = obj as DependencyState; + 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); + } + + /// + /// Generate a hash of this object. + /// + /// Hash of this object. + public override int GetHashCode() { + int hash = 0; + foreach (var file in Files) { + hash ^= file.GetHashCode(); + } + foreach (var pkg in Packages) { + hash ^= pkg.GetHashCode(); + } + foreach (var setting in Settings.Values) { + hash ^= setting.GetHashCode(); + } + return hash; + } + + /// + /// Convert a hashset to a sorted comma separated string. + /// + /// Comma separated string. + 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}) settings=({2})", + SetToString(Packages), SetToString(Files), + DictionaryToString(Settings)); + } + } + + /// + /// Polls a value and signals a callback with the change after the specified delay + /// time. + /// + internal class PropertyPoller { + /// + /// Delegate that is called when a value changes. + /// + /// Previous value of the property that + /// changed. + /// Current value of the property that + /// 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. + private T previousPollValue = default(T); + // Last time the property was polled. + private DateTime previousPollTime = DateTime.Now; + // Time to wait before signalling a change. + private int delayTimeInSeconds; + // Name of the property being polled. + private string propertyName; + // Previous time we checked the property value for a change. + private DateTime previousCheckTime = DateTime.Now; + // Time to wait before checking a property. + private int checkIntervalInSeconds; + + /// + /// Create the poller. + /// + /// 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(string propertyName, + int delayTimeInSeconds = 3, + int checkIntervalInSeconds = 1) { + this.propertyName = propertyName; + this.delayTimeInSeconds = delayTimeInSeconds; + this.checkIntervalInSeconds = checkIntervalInSeconds; + } + + /// + /// Poll the specified value for changes. + /// + /// Delegate that returns the value being polled. + /// Delegate that is called if the value changes. + public void Poll(Func getCurrentValue, Changed changed) { + var currentTime = DateTime.Now; + if (currentTime.Subtract(previousCheckTime).TotalSeconds < + checkIntervalInSeconds) { + return; + } + 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 >= + delayTimeInSeconds) { + Log(String.Format("{0} changed: {1} -> {2}", propertyName, + previousValue, currentValue), + level: LogLevel.Verbose); + changed(previousValue, currentValue); + previousValue = currentValue; + } + } else { + previousPollValue = currentValue; + previousPollTime = currentTime; + } + } + } + } + + /// + /// 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 + + /// + /// Resolver that uses Gradle to download libraries and embed them within a Unity project. + /// Lazy initialize it only when current build target is Android. + /// + private static GradleResolver GradleResolverInstance { + get { + if (gradleResolverInstance == null && + EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) { + gradleResolverInstance = new GradleResolver(); + } + return gradleResolverInstance; + } + } + + /// + /// Instance of GradleResolver. + /// + private static GradleResolver gradleResolverInstance = null; + + /// + /// Resoluton job. + /// This class is used to enqueue a resolution job to execute on the main thread by + /// ScheduleResolve(). + /// It keeps track of whether the job was started via auto-resolution or explicitly. + /// If the job was started via auto-resolution it is removed from the currently scheduled + /// set of jobs when a new job is started via ScheduleResolve(). + /// + private class ResolutionJob { + + /// + /// Whether this is an auto-resolution job, + /// + public bool IsAutoResolveJob { get; private set; } + + /// + /// Action to execute to resolve. + /// + public Action Job { get; private set; } + + /// + /// Initialize this instance. + /// + /// Whether this is an auto-resolution job. + /// Action to execute to resolve. + public ResolutionJob(bool isAutoResolveJob, Action job) { + IsAutoResolveJob = isAutoResolveJob; + Job = job; + } + } + + /// + /// Queue of resolution jobs to execute. + /// + private static List resolutionJobs = new List(); + + /// + /// Flag used to prevent re-entrant auto-resolution. + /// + private static bool autoResolving = false; + + /// + /// ID of the job that executes AutoResolve. + /// + private static int autoResolveJobId = 0; + + /// + /// Polls for changes in the bundle ID. + /// + private static PropertyPoller bundleIdPoller = + new PropertyPoller("Bundle ID"); + + /// + /// Arguments for the bundle ID update event. + /// + public class BundleIdChangedEventArgs : EventArgs { + /// + /// Current project Bundle ID. + /// + public string BundleId { get; set; } + + /// + /// Bundle ID when this event was last fired. + /// + public string PreviousBundleId { get; set; } + } + + /// + /// Event which is fired when the bundle ID is updated. + /// + public static event EventHandler BundleIdChanged; + + /// + /// Asset label applied to files managed by this plugin. + /// + internal const string ManagedAssetLabel = "gpsr"; + + /// + /// Get a boolean property from UnityEditor.EditorUserBuildSettings. + /// + /// Properties are introduced over successive versions of Unity so use reflection to + /// retrieve them. + private static object GetEditorUserBuildSettingsProperty(string name, + object defaultValue) { + var editorUserBuildSettingsType = typeof(UnityEditor.EditorUserBuildSettings); + var property = editorUserBuildSettingsType.GetProperty(name); + if (property != null) { + var value = property.GetValue(null, null); + if (value != null) return value; + } + return defaultValue; + } + + /// + /// Whether the Gradle build system is enabled. + /// + public static bool GradleBuildEnabled { + get { + return GetEditorUserBuildSettingsProperty( + "androidBuildSystem", "").ToString().Equals("Gradle"); + } + } + + /// + /// Whether the Gradle template is enabled. + /// + public static bool GradleTemplateEnabled { + get { + return GradleBuildEnabled && File.Exists(GradleTemplateResolver.GradleTemplatePath); + } + } + + /// + /// 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. + /// + public static bool ProjectExportEnabled { + get { + var value = GetEditorUserBuildSettingsProperty("exportAsGoogleAndroidProject", + null); + return value == null ? false : (bool) value; + } + } + + /// + /// If Gradle project export is enabled. + /// + public static bool GradleProjectExportEnabled { + get { + return PlayServicesResolver.GradleBuildEnabled && + PlayServicesResolver.ProjectExportEnabled; + } + } + + /// + /// Current build system settings. + /// + private struct AndroidBuildSystemSettings { + /// + /// Whether the Gradle build is enabled. + /// + public bool GradleBuildEnabled { get; private set; } + + /// + /// Whether the Gradle template is enabled. + /// + public bool GradleTemplateEnabled { get; private set; } + + /// + /// Whether the Gradle properties template is enabled. + /// + public bool GradlePropertiesTemplateEnabled { get; private set; } + + /// + // Whether project export is enabled. + /// + public bool ProjectExportEnabled { get; private set; } + + /// + /// Get the current build settings. + /// + public static AndroidBuildSystemSettings Current { + get { + return new AndroidBuildSystemSettings { + GradleBuildEnabled = PlayServicesResolver.GradleBuildEnabled, + GradleTemplateEnabled = PlayServicesResolver.GradleTemplateEnabled, + GradlePropertiesTemplateEnabled = PlayServicesResolver.GradlePropertiesTemplateEnabled, + ProjectExportEnabled = PlayServicesResolver.ProjectExportEnabled + }; + } + } + + /// + /// Compare with another AndroidBuildSystemSettings. + /// + /// Object to compare with. + /// true if the object is the same as this, false otherwise. + public override bool Equals(System.Object obj) { + var other = (AndroidBuildSystemSettings)obj; + return other.GradleBuildEnabled == GradleBuildEnabled && + other.GradleTemplateEnabled == GradleTemplateEnabled && + other.GradlePropertiesTemplateEnabled == GradlePropertiesTemplateEnabled && + other.ProjectExportEnabled == ProjectExportEnabled; + } + + /// + /// Generate a hash of this object. + /// + /// Hash of this object. + public override int GetHashCode() { + return GradleBuildEnabled.GetHashCode() ^ GradleTemplateEnabled.GetHashCode() ^ + GradlePropertiesTemplateEnabled.GetHashCode() ^ + ProjectExportEnabled.GetHashCode(); + } + + + /// + /// Convert this object to a string. + /// + /// String representation. + public override string ToString() { + return String.Format("[GradleBuildEnabled={0} GradleTemplateEnabled={1} " + + "GradlePropertiesTemplateEnabled={2} ProjectExportEnabled={2}]", + GradleBuildEnabled, GradleTemplateEnabled, + GradlePropertiesTemplateEnabled, ProjectExportEnabled); + } + } + + /// + /// Polls for changes in build system settings. + /// + private static PropertyPoller androidBuildSystemPoller = + new PropertyPoller("Android Build Settings"); + + /// + /// Arguments for the Android build system changed event. + /// + public class AndroidBuildSystemChangedArgs : EventArgs { + /// + /// Gradle was selected as the build system when this event was fired. + /// + public bool GradleBuildEnabled { get; set; } + + /// + /// Whether Gradle was selected as the build system the last time this event was fired. + /// + public bool PreviousGradleBuildEnabled { get; set; } + + /// + /// Whether a custom Gradle template is enabled. + /// This will only be true if GradleBuildEnabled is also true. + /// + public bool GradleTemplateEnabled { get; set; } + + /// + /// Whether a custom Gradle template was enabled the last time this event was fired. + /// + 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. + /// + public bool ProjectExportEnabled { get; set; } + + /// + /// Whether project export was selected when this event was fired. + /// + public bool PreviousProjectExportEnabled { get; set; } + } + + /// + /// Event which is fired when the Android build system changes. + /// + public static event EventHandler AndroidBuildSystemChanged; + + /// + /// Polls for changes in the Android device ABI. + /// + private static PropertyPoller androidAbisPoller = + 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. + /// + public class AndroidAbisChangedArgs : EventArgs { + /// + /// Target device ABI before it changed. + /// + public string PreviousAndroidAbis { get; set; } + + /// + /// Target device ABI when this event was fired. + /// + public string AndroidAbis { get; set; } + } + + /// + /// Event which is fired when the Android target device ABI changes. + /// + public static event EventHandler AndroidAbisChanged; + + // Parses dependencies from XML dependency files. + private static AndroidXmlDependencies xmlDependencies = new AndroidXmlDependencies(); + + // Last error logged by LogDelegate(). + private static string lastError = null; + + /// + /// Get the Android SDK directory. + /// + public static string AndroidSdkRoot { + get { + 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")) { + var androidPlayerDir = AndroidPlaybackEngineDirectory; + if (!String.IsNullOrEmpty(androidPlayerDir)) { + var androidPlayerSdkDir = Path.Combine(androidPlayerDir, "SDK"); + if (Directory.Exists(androidPlayerSdkDir)) sdkPath = androidPlayerSdkDir; + } + } + + // Pre Unity 2019 Android SDK path. + if (String.IsNullOrEmpty(sdkPath)) { + sdkPath = EditorPrefs.GetString("AndroidSdkRoot"); + } + return sdkPath; + } + } + + /// + /// Polls for changes in AndroidSdkRoot. + /// + private static PropertyPoller androidSdkRootPoller = + new PropertyPoller("Android SDK Path"); + + /// + /// Arguments for the AndroidSdkRootChanged event. + /// + public class AndroidSdkRootChangedArgs : EventArgs { + /// + /// AndroidSdkRoot before it changed. + /// + public string PreviousAndroidSdkRoot { get; set; } + + /// + /// AndroidSdkRoot when this event was fired. + /// + public string AndroidSdkRoot { get; set; } + } + + /// + /// Event which is fired when the Android SDK root changes. + /// + 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 { + 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; + } + } + + /// + /// Initializes the class. + /// + static PlayServicesResolver() { + // 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; + + // Initialize settings and resolve if required. + OnSettingsChanged(); + + // Setup events for auto resolution. + BundleIdChanged += ResolveOnBundleIdChanged; + AndroidBuildSystemChanged += ResolveOnBuildSystemChanged; + AndroidAbisChanged += ResolveOnAndroidAbisChanged; + AndroidSdkRootChanged += ResolveOnAndroidSdkRootChange; + Reresolve(); + + if (SettingsDialogObj.EnableAutoResolution) LinkAutoResolution(); + + return true; + } + + // Unregister events to monitor build system changes for the Android Resolver and other + // plugins. + public static void UnlinkAutoResolution() { + RunOnMainThread.OnUpdate -= PollBuildSystem; + RunOnMainThread.OnUpdate -= PollAndroidAbis; + RunOnMainThread.OnUpdate -= PollAndroidSdkRoot; + } + + // Register events to monitor build system changes for the Android Resolver and other + // plugins. + public static void LinkAutoResolution() { + UnlinkAutoResolution(); + RunOnMainThread.OnUpdate += PollBuildSystem; + RunOnMainThread.OnUpdate += PollAndroidAbis; + RunOnMainThread.OnUpdate += PollAndroidSdkRoot; + } + + /// + /// Called from PlayServicesSupport to log a message. + /// + internal static void LogDelegate(string message, PlayServicesSupport.LogLevel level) { + Google.LogLevel loggerLogLevel = Google.LogLevel.Info; + switch (level) { + case PlayServicesSupport.LogLevel.Info: + loggerLogLevel = Google.LogLevel.Info; + break; + case PlayServicesSupport.LogLevel.Warning: + loggerLogLevel = Google.LogLevel.Warning; + break; + case PlayServicesSupport.LogLevel.Error: + loggerLogLevel = Google.LogLevel.Error; + break; + default: + break; + } + Log(message, level: loggerLogLevel); + } + + /// + /// Get the application ID for the Android build target. + /// + /// Application / bundle ID for the Android build target. + internal static string GetAndroidApplicationId() { + return UnityCompat.GetApplicationId(BuildTarget.Android); + } + + /// + /// Log a filtered message to Unity log, error messages are stored in + /// PlayServicesSupport.lastError. + /// + /// String to write to the log. + /// Severity of the message, if this is below the currently selected + /// Level property the message will not be logged. + internal static void Log(string message, Google.LogLevel level = LogLevel.Info) { + if (level == LogLevel.Error) lastError = message; + logger.Log(message, level: level); + } + + /// + /// Patterns of files that are monitored to trigger auto resolution. + /// + private static HashSet autoResolveFilePatterns = new HashSet(); + + /// + /// Add file patterns to monitor to trigger auto resolution. + /// + /// Set of file patterns to monitor to trigger auto + /// resolution. + public static void AddAutoResolutionFilePatterns(IEnumerable patterns) { + autoResolveFilePatterns.UnionWith(patterns); + } + + /// + /// Utility function to check a set of files to see whether resolution should be + /// triggered. + /// + /// True if auto-resolve was triggered. + private static bool CheckFilesForAutoResolution(HashSet filesToCheck) { + bool resolve = false; + foreach (var asset in filesToCheck) { + foreach (var pattern in autoResolveFilePatterns) { + if (pattern.Match(asset).Success) { + Log(String.Format("Found asset {0} matching {1}, attempting " + + "auto-resolution.", + asset, pattern.ToString()), + level: LogLevel.Verbose); + resolve = true; + break; + } + } + } + if (resolve) Reresolve(); + + return resolve; + } + + /// + /// Called by Unity when all assets have been updated. This + /// is used to kick off resolving the dependencies declared. + /// + /// 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 (GradleResolverInstance != null) { + // If the manifest changed, try patching it. + var manifestPath = FileUtils.NormalizePathSeparators( + SettingsDialogObj.AndroidManifestPath); + foreach (var importedAsset in importedAssets) { + if (FileUtils.NormalizePathSeparators(importedAsset) == manifestPath) { + PatchAndroidManifest(GetAndroidApplicationId(), null); + break; + } + } + + if (AutomaticResolutionEnabled) { + // If anything has been removed from the packaging directory schedule + // resolution. + foreach (string asset in deletedAssets) { + if (asset.StartsWith(SettingsDialogObj.PackageDir)) { + Reresolve(); + return; + } + } + // Schedule a check of imported assets. + if (importedAssets.Length > 0 && autoResolveFilePatterns.Count > 0) { + if (CheckFilesForAutoResolution(new HashSet(importedAssets))) { + return; + } + } + // Check deleted assets to see if we need to trigger an auto-resolve. + if (deletedAssets.Length > 0 && autoResolveFilePatterns.Count > 0) { + if (CheckFilesForAutoResolution(new HashSet(deletedAssets))) { + return; + } + } + } + } + } + + /// + /// Number of scenes processed by OnPostProcessScene() since this DLL was loaded into the + /// app domain. + /// This is present so that it's possible to easily log the number of times + /// OnPostProcessScene() is called when building for the selected target platform. + /// If this value ends up larger than the total number of scenes included in the build + /// then the behavior of PostProcessSceneAttribute has changed and should be investigated. + /// If this value continues to increase between each build for a target platform then + /// Unity's behavior has changed such that this module is no longer being reloaded in the + /// app domain so we can't rely upon this method of detecting the first scene in the build. + /// + 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. + /// + [UnityEditor.Callbacks.PostProcessSceneAttribute(0)] + private static void OnPostProcessScene() { + // If we're in the editor play mode, do nothing. + if (UnityEngine.Application.isPlaying) return; + // If the Android resolver isn't enabled or automatic resolution is disabled, + // do nothing. + if (GradleResolverInstance == null || !SettingsDialogObj.AutoResolveOnBuild) { + return; + } + // If post-processing has already been executed since this module was loaded, don't + // do so again. + scenesProcessed++; + if (scenesProcessed > 1) return; + Log("Starting auto-resolution before scene build...", level: LogLevel.Verbose); + 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); + } + + /// + /// Schedule auto-resolution. + /// + /// Time to wait until running AutoResolve(). + /// Defaults to 1 second. + private static void ScheduleAutoResolve(double delayInMilliseconds = 1000.0) { + lock (typeof(PlayServicesResolver)) { + if (!autoResolving) { + RunOnMainThread.Cancel(autoResolveJobId); + autoResolveJobId = RunOnMainThread.Schedule( + () => { + lock (typeof(PlayServicesResolver)) { + autoResolving = true; + } + RunOnMainThread.PollOnUpdateUntilComplete(() => { + if (EditorApplication.isCompiling) return false; + // Only run AutoResolve() if we have a valid autoResolveJobId. + // If autoResolveJobId is 0, ScheduleResolve() + // has already been run and we should not run AutoResolve() + // again. + if (autoResolveJobId != 0) { + AutoResolve(() => { + lock (typeof(PlayServicesResolver)) { + autoResolving = false; + autoResolveJobId = 0; + } + }); + } + return true; + }); + }, + delayInMilliseconds); + } + } + } + + /// + /// Resolve dependencies if auto-resolution is enabled. + /// + /// Called when resolution is complete. + private static void AutoResolve(Action resolutionComplete) { + if (AutomaticResolutionEnabled) { + ScheduleResolve( + false, false, (success) => { + if (resolutionComplete != null) resolutionComplete(); + }, true); + } + else if (!ExecutionEnvironment.InBatchMode && + 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 > 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 > External Dependency Manager > Android Resolver > " + + "Settings\" and check \"Enable Auto-resolution\""); + resolutionComplete(); + } + } + + /// + /// Auto-resolve if any packages need to be resolved. + /// + private static void Reresolve() { + if (AutomaticResolutionEnabled && GradleResolverInstance != null) { + ScheduleAutoResolve(); + } + } + + /// + /// Replace attribute values in a tree of XmlElement objects. + /// + /// Element to traverse. + /// Dictionary of attribute values to replace where + /// each value in the dictionary replaces the manifest attribute value corresponding to + /// the key in the dictionary. For example, {"foo", "bar"} results in all instances of + /// "foo" attribute values being replaced with "bar". NOTE: Partial replacements are + /// applied if the attribute value starts with the dictionary key. For example, + /// {"com.my.app", "com.another.app"} will change... + /// * com.my.app.service --> com.another.app.service + /// * 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 "<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( + XmlElement node, + Dictionary attributeValueReplacements, + string path = null) { + bool replacementsApplied = false; + if (path == null) path = node.Name; + // Build a dictionary of attribute value replacements. + var attributeNamesAndValues = new Dictionary(); + foreach (var attribute in node.Attributes) { + // Skip non-XmlAttribute objects. + var xmlAttribute = attribute as XmlAttribute; + if (xmlAttribute == null) continue; + var attributeName = xmlAttribute.Name; + var attributeValue = xmlAttribute.Value; + foreach (var kv in attributeValueReplacements) { + bool isVariable = kv.Key.StartsWith("${") && kv.Key.EndsWith("}"); + if ((attributeValue.StartsWith(kv.Key) || + (isVariable && attributeValue.Contains(kv.Key))) && + attributeValue != kv.Value) { + // If this is performing a variable replacement, replace all instances of + // the variable e.g: + // If we're doing replacing ${foo} with "baz" and the attribute value is + // "fee.${foo}.bar.${foo}" this yields "fee.baz.bar.baz" + // + // If this is performing a literal value replacement, replace only the + // first instance of the value at the start of the string e.g: + // If we're replacing a.neat.game.rocks with a.neater.game.rocks and the + // attribute value is "a.neat.game.rocks.part1" this yields + // "a.neater.game.rocks.part1". + attributeNamesAndValues[attributeName] = + isVariable ? + attributeValue.Replace(kv.Key, kv.Value) : + kv.Value + attributeValue.Substring(kv.Key.Length); + break; + } + } + } + // Replace attribute values. + foreach (var kv in attributeNamesAndValues) { + Log(String.Format("Replacing element: {0} attribute: {1} value: {2} --> {3}", + path, kv.Key, node.GetAttribute(kv.Key), kv.Value), + level: LogLevel.Verbose); + node.SetAttribute(kv.Key, kv.Value); + replacementsApplied = true; + } + // Traverse child tree and apply replacements. + foreach (var child in node.ChildNodes) { + // Comment elements cannot be cast to XmlElement so ignore them. + var childElement = child as XmlElement; + if (childElement == null) continue; + replacementsApplied |= ReplaceVariablesInXmlElementTree( + childElement, attributeValueReplacements, + path: path + "/" + childElement.Name); + } + return replacementsApplied; + } + + /// + /// Replaces the variables in the AndroidManifest file. + /// + /// Path of the manifest file. + /// Bundle ID used to replace instances of ${applicationId} in + /// attribute values. + /// Dictionary of attribute values to replace where + /// each value in the dictionary replaces the manifest attribute value corresponding to + /// the key in the dictionary. For example, {"foo", "bar"} results in all instances of + /// "foo" attribute values being replaced with "bar". NOTE: Partial replacements are + /// applied if the attribute value starts with the dictionary key. For example, + /// {"com.my.app", "com.another.app"} will change... + /// * com.my.app.service --> com.another.app.service + /// * foo.com.my.app.service --> foo.com.my.app.service (unchanged) + /// + internal static void ReplaceVariablesInAndroidManifest( + string androidManifestPath, string bundleId, + Dictionary attributeValueReplacements) { + if (!File.Exists(androidManifestPath)) return; + attributeValueReplacements["${applicationId}"] = bundleId; + + // Read manifest. + Log(String.Format("Reading AndroidManifest {0}", androidManifestPath), + level: LogLevel.Verbose); + var manifest = new XmlDocument(); + using (var stream = new StreamReader(androidManifestPath)) { + manifest.Load(stream); + } + + // Log list of replacements that will be applied. + var replacementsStringList = new List(); + foreach (var kv in attributeValueReplacements) { + replacementsStringList.Add(String.Format("{0} --> {1}", kv.Key, kv.Value)); + } + Log(String.Format("Will apply attribute value replacements:\n{0}", + String.Join("\n", replacementsStringList.ToArray())), + level: LogLevel.Verbose); + + // Apply replacements. + if (ReplaceVariablesInXmlElementTree(manifest.DocumentElement, + attributeValueReplacements)) { + // Write out modified XML document. + using (var xmlWriter = XmlWriter.Create( + androidManifestPath, + new XmlWriterSettings { + Indent = true, + IndentChars = " ", + NewLineChars = "\n", + NewLineHandling = NewLineHandling.Replace + })) { + manifest.Save(xmlWriter); + } + Log(String.Format("Saved changes to {0}", androidManifestPath), + level: LogLevel.Verbose); + } + } + + /// + /// Apply variable expansion in the AndroidManifest.xml file. + /// + private static void PatchAndroidManifest(string bundleId, string previousBundleId) { + 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(); + Log(String.Format("Patch Android Manifest with new bundle ID {0} -> {1}", + previousBundleId, bundleId), + level: LogLevel.Verbose); + if (!(String.IsNullOrEmpty(previousBundleId) || String.IsNullOrEmpty(bundleId))) { + replacements[previousBundleId] = bundleId; + } + ReplaceVariablesInAndroidManifest(SettingsDialogObj.AndroidManifestPath, + bundleId, replacements); + } + + /// + /// If the user changes the bundle ID, perform resolution again. + /// + private static void ResolveOnBundleIdChanged(object sender, + BundleIdChangedEventArgs args) { + PatchAndroidManifest(args.BundleId, args.PreviousBundleId); + Reresolve(); + } + + /// + /// If the user changes the bundle ID, perform resolution again. + /// + private static void PollBundleId() { + bundleIdPoller.Poll(() => GetAndroidApplicationId(), (previousValue, currentValue) => { + if (BundleIdChanged != null) { + BundleIdChanged(null, new BundleIdChangedEventArgs { + PreviousBundleId = previousValue, + BundleId = currentValue + }); + } + }); + } + + /// + /// If the user changes the Android build system, perform resolution again. + /// + private static void ResolveOnBuildSystemChanged(object sender, + AndroidBuildSystemChangedArgs args) { + Reresolve(); + } + + /// + /// Poll the Android build system selection for changes. + /// + private static void PollBuildSystem() { + androidBuildSystemPoller.Poll( + () => AndroidBuildSystemSettings.Current, + (previousValue, currentValue) => { + if (AndroidBuildSystemChanged != null) { + AndroidBuildSystemChanged(null, new AndroidBuildSystemChangedArgs { + GradleBuildEnabled = currentValue.GradleBuildEnabled, + PreviousGradleBuildEnabled = previousValue.GradleBuildEnabled, + GradleTemplateEnabled = currentValue.GradleTemplateEnabled, + PreviousGradleTemplateEnabled = previousValue.GradleTemplateEnabled, + GradlePropertiesTemplateEnabled = currentValue.GradlePropertiesTemplateEnabled, + PreviousGradlePropertiesTemplateEnabled = previousValue.GradlePropertiesTemplateEnabled, + ProjectExportEnabled = currentValue.ProjectExportEnabled, + PreviousProjectExportEnabled = previousValue.ProjectExportEnabled, + }); + } + }); + } + + /// + /// Poll the Android ABIs for changes. + /// + private static void PollAndroidAbis() { + androidAbisPoller.Poll(() => AndroidAbis.Current, (previousValue, currentValue) => { + if (AndroidAbisChanged != null) { + AndroidAbisChanged(null, new AndroidAbisChangedArgs { + PreviousAndroidAbis = previousValue.ToString(), + AndroidAbis = currentValue.ToString() + }); + } + }); + } + + /// + /// Hide shared libraries from Unity's build system that do not target the currently + /// selected ABI. + /// + private static void ResolveOnAndroidAbisChanged( + object sender, AndroidAbisChangedArgs args) { + Reresolve(); + } + + /// + /// Poll the Android SDK path for changes. + /// + private static void PollAndroidSdkRoot() { + androidSdkRootPoller.Poll(() => AndroidSdkRoot, (previousValue, currentValue) => { + if (AndroidSdkRootChanged != null) { + AndroidSdkRootChanged(null, new AndroidSdkRootChangedArgs { + PreviousAndroidSdkRoot = previousValue, + AndroidSdkRoot = currentValue + }); + } + }); + } + + /// + /// Run Android resolution when the Android SDK path changes. + /// + private static void ResolveOnAndroidSdkRootChange( + object sender, AndroidSdkRootChangedArgs args) { + ScheduleResolve(true, false, null, true); + } + + /// + /// Delete the specified array of files and directories. + /// + /// Files or directories to delete. + /// true if files are deleted, false otherwise. + 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)) { + Log(deleteError, level: LogLevel.Warning); + } + bool deletedFiles = failedToDelete.Count != (new List(filenames)).Count; + if (deletedFiles) AssetDatabase.Refresh(); + return deletedFiles; + } + + /// + /// Signal completion of a resolve job. + /// + /// Action to call. + private static void SignalResolveJobComplete(Action completion) { + resolutionJobs.RemoveAll((jobInfo) => { return jobInfo == null; }); + completion(); + ExecuteNextResolveJob(); + } + + /// + /// Execute the next resolve job on the queue. + /// + private static void ExecuteNextResolveJob() { + Action nextJob = null; + lock (resolutionJobs) { + while (resolutionJobs.Count > 0) { + // Remove any terminators from the queue. + var jobInfo = resolutionJobs[0]; + resolutionJobs.RemoveAt(0); + if (jobInfo != null) { + nextJob = jobInfo.Job; + // Keep an item in the queue to indicate resolution is in progress. + resolutionJobs.Add(null); + break; + } + } + } + if (nextJob != null) nextJob(); + } + + /// + /// Resolve dependencies. If resolution is currently active this queues up the requested + /// resolution action to execute when the current resolution is complete. + /// + /// Delegate called when resolution is complete. + /// Whether resolution should be executed when no dependencies + /// have changed. This is useful if a dependency specifies a wildcard in the version + /// expression. + /// Delegate called when resolution is complete + /// with a parameter that indicates whether it succeeded or failed. + public static void Resolve(Action resolutionComplete = null, + bool forceResolution = false, + Action resolutionCompleteWithResult = null) { + ScheduleResolve(forceResolution, false, + (success) => { + if (resolutionComplete != null) { + resolutionComplete(); + } + if (resolutionCompleteWithResult != null) { + resolutionCompleteWithResult(success); + } + }, false); + } + + /// + /// Wait for a ManualResetEvent to complete. + /// + /// Event to poll until it's complete. + private static void PollManualResetEvent(ManualResetEvent eventToPoll) { + // We poll from this thread to pump the update queue. + while (true) { + RunOnMainThread.TryExecuteAll(); + if (eventToPoll.WaitOne(100 /* 100ms poll interval */)) { + break; + } + } + } + + /// + /// 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. + /// Whether to unconditionally close the resolution + /// window when complete. + /// true if successful, false otherwise. + private static bool ResolveSync(bool forceResolution, bool closeWindowOnCompletion) { + bool successful = false; + var completeEvent = new ManualResetEvent(false); + ScheduleResolve(forceResolution, closeWindowOnCompletion, (success) => { + successful = success; + completeEvent.Set(); + }, false); + PollManualResetEvent(completeEvent); + 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 (AutomaticResolutionEnabled) { + Log("Disabling auto-resolution to prevent libraries from being " + + "resolved after deletion.", level: LogLevel.Warning); + SettingsDialogObj.EnableAutoResolution = false; + } + PlayServicesResolver.analytics.Report("/deleteresolved", + "Delete Resolved Packages"); + DeleteLabeledAssets(); + DeleteResolvedLibrariesFromGradleTemplate(updateMainTemplate: true, + updateSettingsTemplate: true); + if (complete != null) complete(); + }, 0); + } + + /// + /// Delete all resolved libraries synchronously. + /// + public static void DeleteResolvedLibrariesSync() { + var completeEvent = new ManualResetEvent(false); + DeleteResolvedLibraries(complete: () => { completeEvent.Set(); }); + PollManualResetEvent(completeEvent); + } + + /// + /// Schedule resolution of dependencies. If resolution is currently active this queues up + /// the requested resolution action to execute when the current resolution is complete. + /// All queued auto-resolution jobs are canceled before enqueuing a new job. + /// + /// 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, bool closeWindowOnCompletion, + Action resolutionCompleteWithResult, + bool isAutoResolveJob) { + bool firstJob; + lock (resolutionJobs) { + // Remove the scheduled action which enqueues an auto-resolve job. + RunOnMainThread.Cancel(autoResolveJobId); + autoResolveJobId = 0; + // Remove any enqueued auto-resolve jobs. + resolutionJobs.RemoveAll((jobInfo) => { + return jobInfo != null && jobInfo.IsAutoResolveJob; + }); + firstJob = resolutionJobs.Count == 0; + + resolutionJobs.Add( + new ResolutionJob( + isAutoResolveJob, + () => { + ResolveUnsafeAfterMainTemplateCheck( + (success) => { + SignalResolveJobComplete(() => { + if (resolutionCompleteWithResult != null) { + resolutionCompleteWithResult(success); + } + }); + }, + forceResolution, + isAutoResolveJob, + closeWindowOnCompletion); + })); + } + if (firstJob) ExecuteNextResolveJob(); + } + + /// + /// 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. + /// 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); + }); + } + + /// + /// 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 && + !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); + SettingsDialogObj.ExplodeAars = true; + } + + xmlDependencies.ReadAll(logger); + + // If no dependencies are present, skip the resolution step. + if (PlayServicesSupport.GetAllDependencies().Count == 0) { + Log("No dependencies found.", level: LogLevel.Verbose); + if (PlayServicesResolver.FindLabeledAssets() != null) { + Log("Stale dependencies exist. Deleting assets...", level: LogLevel.Verbose); + DeleteLabeledAssets(); + } + DeleteResolvedLibrariesFromGradleTemplate(updateMainTemplate: true, + updateSettingsTemplate: true); + + if (resolutionComplete != null) { + resolutionComplete(true); + } + return; + } + + // 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 (SettingsDialogObj.PromptBeforeAutoResolution && + isAutoResolveJob && + !ExecutionEnvironment.InBatchMode) { + 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; + } + if (shouldResolve) { + ResolveUnsafe(resolutionComplete, forceResolution, + closeWindowOnCompletion); + } else { + if (resolutionComplete != null) { + resolutionComplete(false); + } + } + }); + } 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; + } + Log(String.Format("Android dependencies changed from:\n" + + "{0}\n\n" + + "to:\n" + + "{1}\n", + previousState.ToString(), + currentState.ToString()), + level: LogLevel.Verbose); + // 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); + } + } + + 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( + 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(); + 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 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(() => { + var dependencies = PlayServicesSupport.GetAllDependencies().Values; + finishResolution( + GradleTemplateResolver.InjectDependencies(dependencies) && + patchGradleProperties() && + patchSettingsTemplateGradle(dependencies), lastError); + }); + } else { + 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?"); + }); + } + } + } + + /// + /// Display a dialog explaining that the resolver is disabled in the current configuration. + /// + private static void NotAvailableDialog() { + DialogWindow.Display("Android Resolver.", + "Resolver not enabled. Android platform must be selected.", + DialogWindow.Option.Selected0, "OK"); + + } + + /// + /// Link to the documentation. + /// + [MenuItem("Assets/External Dependency Manager/Android Resolver/Documentation")] + public static void OpenDocumentation() { + Application.OpenURL(VersionHandlerImpl.DocumentationUrl("#android-resolver-usage")); + } + + /// + /// Add a menu item for resolving the jars manually. + /// + [MenuItem("Assets/External Dependency Manager/Android Resolver/Settings")] + public static void SettingsDialog() + { + 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 (GradleResolverInstance == null) { + NotAvailableDialog(); + return; + } + ScheduleResolve( + 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."), + DialogWindow.Option.Selected0, "OK"); + }, false); + } + + /// + /// Add a menu item for resolving the jars manually. + /// + [MenuItem("Assets/External Dependency Manager/Android Resolver/Resolve")] + public static void MenuResolve() { + ExecuteMenuResolve(false); + } + + /// + /// Add a menu item to force resolve the jars manually. + /// + [MenuItem("Assets/External Dependency Manager/Android Resolver/Force Resolve")] + public static void MenuForceResolve() { + ExecuteMenuResolve(true); + } + + /// + /// Add a menu item to clear all resolved libraries. + /// + [MenuItem("Assets/External Dependency Manager/Android Resolver/Delete Resolved Libraries")] + public static void MenuDeleteResolvedLibraries() { + DeleteResolvedLibrariesSync(); + } + + /// + /// If dependencies is specified return the value, otherwise refresh from the project and + /// return the parsed dependencies. + /// + /// List of Android library dependencies. + private static IEnumerable GetOrReadDependencies( + IEnumerable dependencies) { + if (dependencies == null) { + xmlDependencies.ReadAll(logger); + dependencies = PlayServicesSupport.GetAllDependencies().Values; + } + return dependencies; + } + + /// + /// Get the list of Android package specs referenced by the project and the sources they're + /// loaded from. + /// + /// List of package spec, source pairs. + public static IList> GetPackageSpecs( + IEnumerable dependencies = null) { + return new List>(new SortedList( + GradleResolver.DependenciesToPackageSpecs(GetOrReadDependencies(dependencies)))); + } + + /// + /// Get the list of Maven repo URIs required for Android libraries in this project. + /// + /// List of repo, source pairs. + public static IList> GetRepos( + IEnumerable dependencies = null) { + return GradleResolver.DependenciesToRepoUris(GetOrReadDependencies(dependencies)); + } + + /// + /// 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 GradleMavenReposLines(ICollection dependencies) { + var lines = new List(); + if (dependencies.Count > 0) { + 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("}"); + } + return lines; + } + + /// + /// Get the included dependencies as lines that can be included in a Gradle file. + /// + /// Set of dependencies to convert to package specs. + /// Whether to include the "dependencies {" block + /// scope in the returned list. + /// Lines that can be included in a gradle file. + 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)) { + 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/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)); + lines.AddRange(GradleDependenciesLines(dependencies)); + lines.AddRange(PackagingOptionsLines(dependencies)); + var dependenciesString = String.Join("\n", lines.ToArray()); + Log(dependenciesString); + var window = TextAreaDialog.CreateTextAreaDialog("Android Libraries"); + window.bodyText = dependenciesString; + window.Show(); + } + + /// + /// Label a set of assets that should be managed by this plugin. + /// + /// Set of assets to label. + /// Called when the operation is complete with the set of assets + /// that could not be labeled. + /// Whether to block until asset labeling is complete. + /// Called with the progress (0..1) and message that indicates + /// processing progress. + /// Whether to display a warning if assets can't be + /// labeled. + /// Whether to label assets in subdirectories of the specified + /// assetPaths. + /// + internal static void LabelAssets(IEnumerable assetPaths, + Action> complete = null, + bool synchronous = true, + Action progressUpdate = null, + bool displayWarning = true, + bool recursive = false) { + var assetsWithoutAssetImporter = new HashSet(); + var projectDataFolder = Path.GetFullPath(Application.dataPath); + var assetsToProcess = new List(assetPaths); + int totalAssets = assetsToProcess.Count; + RunOnMainThread.PollOnUpdateUntilComplete(() => { + var remainingAssets = assetsToProcess.Count; + // 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 + // are not in the project. + var fullAssetPath = Path.GetFullPath(assetPath); + if (assetPath.EndsWith(".meta") || + !fullAssetPath.StartsWith(projectDataFolder)) { + return false; + } + + // Get the relative path of this asset. + var relativeAssetPath = Path.Combine( + Path.GetFileName(projectDataFolder), + fullAssetPath.Substring(projectDataFolder.Length +1)); + + if (progressUpdate != null) { + progressUpdate((float)(totalAssets - remainingAssets) / + (float)totalAssets, relativeAssetPath); + } + + // If the asset is a directory, add labels to the contents. + if (recursive && Directory.Exists(relativeAssetPath)) { + var contents = new List( + Directory.GetFileSystemEntries(relativeAssetPath)); + totalAssets += contents.Count; + assetsToProcess.AddRange(contents); + return false; + } + + // It's likely files have been added or removed without using AssetDatabase + // methods so (re)import the asset to make sure it's in the AssetDatabase. + AssetDatabase.ImportAsset(relativeAssetPath, + options: ImportAssetOptions.ForceSynchronousImport); + + // Add the label to the asset. + AssetImporter importer = AssetImporter.GetAtPath(relativeAssetPath); + if (importer != null) { + var labels = new HashSet(AssetDatabase.GetLabels(importer)); + labels.Add(ManagedAssetLabel); + AssetDatabase.SetLabels(importer, (new List(labels)).ToArray()); + } 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() { + return VersionHandlerImpl.SearchAssetDatabase("l:" + ManagedAssetLabel); + } + + /// + /// Delete the full set of assets managed from this plugin. + /// This is used for uninstalling or switching between resolvers which maintain a different + /// set of assets. + /// + 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. + /// 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, IEnumerable extractFilenames, + string outputDirectory, bool update) { + try { + string zipPath = Path.GetFullPath(zipFile); + 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( + JavaUtilities.JarBinaryPath, + String.Format(" xvf \"{0}\" {1}", zipPath, extractFilesArg), + workingDirectory: outputDirectory); + if (result.exitCode != 0) { + Log(String.Format("Error extracting \"{0}\"\n" + + "{1}", zipPath, result.message), level: LogLevel.Error); + return false; + } + } + catch (Exception e) { + Log(String.Format("Failed with exception {0}", e.ToString())); + throw e; + } + return true; + } + } +} 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/AndroidResolver/src/TextAreaDialog.cs b/source/AndroidResolver/src/TextAreaDialog.cs new file mode 100644 index 00000000..93eadb13 --- /dev/null +++ b/source/AndroidResolver/src/TextAreaDialog.cs @@ -0,0 +1,303 @@ +// +// 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 GooglePlayServices +{ + using System; + using UnityEditor; + using UnityEngine; + + using Google; + + /// + /// Window which displays a scrollable text area and two buttons at the bottom. + /// + public class TextAreaDialog : EditorWindow + { + /// + /// Delegate type, called when a button is clicked. + /// + public delegate void ButtonClicked(TextAreaDialog dialog); + + /// + /// Delegate called when a button is clicked. + /// + public ButtonClicked buttonClicked; + + /// + /// Whether this window should be modal. + /// NOTE: This emulates modal behavior by re-acquiring focus when it's lost. + /// + public bool modal = true; + + /// + /// Set the text to display in the summary area of the window. + /// + public string summaryText = ""; + + /// + /// Whether to display summary text. + /// + public bool summaryTextDisplay = true; + + /// + /// Set the text to display on the "yes" (left-most) button. + /// + public string yesText = ""; + + /// + /// Set the text to display on the "no" (right-most) button. + /// + public string noText = ""; + + /// + /// Set the text to display in the scrollable text area. + /// + public string bodyText = ""; + + /// + /// Result of yes / no button press. true if the "yes" button was pressed, false if the + /// "no" button was pressed. Defaults to "false". + /// + public bool result = false; + + /// + /// Whether either button was clicked. + /// + private bool yesNoClicked = false; + + /// + /// Current position of the scrollbar. + /// + public Vector2 scrollPosition; + + /// + /// Whether to automatically scroll to the bottom of the window. + /// + public volatile bool autoScrollToBottom; + + /// + /// Last time the window was repainted. + /// + private long lastRepaintTimeInMilliseconds = 0; + + /// + /// Minimum repaint period. + /// + 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. + /// + /// Title to display on the window. + /// Reference to this class + public static TextAreaDialog CreateTextAreaDialog(string title) + { + TextAreaDialog window = (TextAreaDialog)EditorWindow.GetWindow(typeof(TextAreaDialog), + true, title, true); + window.Initialize(); + return window; + } + + public virtual void Initialize() + { + yesText = ""; + noText = ""; + summaryText = ""; + bodyText = ""; + result = false; + yesNoClicked = false; + scrollPosition = new Vector2(0, 0); + 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) { + RunOnMainThread.Run(() => { + bodyText += text; + Repaint(); + }); + } + + /// + /// Alternative Repaint() method that does not crash in batch mode and throttles repaint + /// rate to REPAINT_PERIOD_IN_MILLISECONDS. + /// + public new void Repaint() { + if (!ExecutionEnvironment.InBatchMode) { + // Throttle repaint to REPAINT_PERIOD_IN_MILLISECONDS. + var timeInMilliseconds = (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond); + var timeElapsedInMilliseconds = timeInMilliseconds - lastRepaintTimeInMilliseconds; + if (timeElapsedInMilliseconds >= REPAINT_PERIOD_IN_MILLISECONDS) { + lastRepaintTimeInMilliseconds = timeInMilliseconds; + if (autoScrollToBottom) scrollPosition.y = Mathf.Infinity; + base.Repaint(); + } + } + } + + // Draw the GUI. + protected virtual void OnGUI() + { + EditorGUILayout.BeginVertical(); + + if (!String.IsNullOrEmpty(summaryText) && summaryTextDisplay) { + EditorGUILayout.LabelField(summaryText, EditorStyles.boldLabel); + } + + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + // Unity text elements can only display up to a small X number of characters (rumors + // are ~65k) so generate a set of labels one for each subset of the text being + // displayed. + int bodyTextOffset = 0; + System.Collections.Generic.List bodyTextList = + new System.Collections.Generic.List(); + const int chunkSize = 5000; // Conservative chunk size < 65k characters. + while (bodyTextOffset < bodyText.Length) + { + 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) + { + float pixelHeight = EditorStyles.wordWrappedLabel.CalcHeight( + new GUIContent(bodyTextChunk), position.width); + EditorGUILayout.SelectableLabel(bodyTextChunk, + EditorStyles.wordWrappedLabel, + GUILayout.Height(pixelHeight)); + } + EditorGUILayout.EndScrollView(); + + bool yesPressed = false; + bool noPressed = false; + EditorGUILayout.BeginHorizontal(); + if (yesText != "") yesPressed = GUILayout.Button(yesText); + if (noText != "") noPressed = GUILayout.Button(noText); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.EndVertical(); + + // If yes or no buttons were pressed, call the buttonClicked delegate. + if (yesPressed || noPressed) + { + yesNoClicked = true; + if (yesPressed) + { + result = true; + } + else if (noPressed) + { + result = false; + } + if (buttonClicked != null) buttonClicked(this); + } + } + + // Optionally make the dialog modal. + protected virtual void OnLostFocus() + { + if (modal) Focus(); + } + + // If the window is destroyed click the no button if a listener is attached. + protected virtual void OnDestroy() { + if (!yesNoClicked) { + result = false; + 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/AndroidResolver/src/UnityCompat.cs b/source/AndroidResolver/src/UnityCompat.cs new file mode 100644 index 00000000..265e19fb --- /dev/null +++ b/source/AndroidResolver/src/UnityCompat.cs @@ -0,0 +1,535 @@ +// +// Copyright (C) 2017 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 Google; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace GooglePlayServices { +// TODO(butterfield): Move to a new assembly, for common use between plugins. + +// Provides an API for accessing Unity APIs that work across various verisons of Unity. +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 int DEFAULT_ANDROID_MIN_SDK = 14; + private const int DEFAULT_PLATFORM_VERSION = 25; + + 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 = + "Please report this as a bug with the version of Unity you are using at: " + + "https://github.com/googlesamples/unity-jar-resolver/issues"; + + private static int MinSDKVersionFallback { + get { return EditorPrefs.GetInt(ANDROID_MIN_SDK_FALLBACK_KEY, DEFAULT_ANDROID_MIN_SDK); } + } + private static int AndroidPlatformVersionFallback { + get { return EditorPrefs.GetInt(ANDROID_PLATFORM_FALLBACK_KEY, DEFAULT_PLATFORM_VERSION); } + } + + // Parses a UnityEditor.AndroidSDKVersion enum for a value. + 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); + } + + if (enumName == "Auto") { + return -1; + } + + int versionVal; + bool validVersionString = Int32.TryParse(enumName, out versionVal); + if (!validVersionString) { + Debug.LogError("Could not determine the Android SDK Version from the Unity " + + "version enum. Resorting to reading a fallback value from the editor " + + "preferences " + fallbackPrefKey + ": " + + fallbackValue.ToString() + ". " + + WRITE_A_BUG); + } + return validVersionString ? versionVal : fallbackValue; + } + + /// + /// Parses the MinSDKVersion as an int from the Android Unity Player Settings. + /// + /// + /// The Unity API is supported all the way back to Unity 4.0, but results returned may need a + /// different interpretation in future versions to give consistent versions. + /// + /// the sdk value (ie. 24 for Android 7.0 Nouget) + public static int GetAndroidMinSDKVersion() { + int minSdkVersion = VersionFromAndroidSDKVersionsEnum( + (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. + /// + /// + /// This was added in Unity 5.6, so previous versions actually use reflection to get the value + /// 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). 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"); + int apiLevel = property == null ? -1 : + VersionFromAndroidSDKVersionsEnum( + 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; + } + + /// + /// Returns whether the editor is running in batch mode. + /// + [ObsoleteAttribute("InBatchMode is obsolete, use ExecutionEnvironment.InBatchMode instead")] + public static bool InBatchMode { get { return ExecutionEnvironment.InBatchMode; } } + + private static Type AndroidExternalToolsClass { + get { + 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 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 AndroidJavaToolsInstance { + get { + 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); + } + } + + 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; + } + } + + private static Type PostProcessAndroidPlayerClass { + get { + Type sdkTools = Type.GetType( + UNITY_ANDROID_POST_PROCESS_ANDROID_PLAYER_CLASS + ", " + + UNITY_ANDROID_EXTENSION_ASSEMBLY); + if (sdkTools == null) { + Debug.LogError("Could not find the " + + UNITY_ANDROID_POST_PROCESS_ANDROID_PLAYER_CLASS + + " class via reflection. " + WRITE_A_BUG); + return null; + } + return sdkTools; + } + } + + private static object PostProcessAndroidPlayerInstance { + get { + Type androidPlayerClass = PostProcessAndroidPlayerClass; + ConstructorInfo constructor = null; + if (androidPlayerClass != null) + constructor = androidPlayerClass.GetConstructor( + BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] {}, null); + if (constructor == null) { + Debug.LogError("Could not find the " + + UNITY_ANDROID_POST_PROCESS_ANDROID_PLAYER_CLASS + + " constructor via reflection. " + WRITE_A_BUG); + return null; + } + + return constructor.Invoke(null); + } + } + + // 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. + 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. + 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); + } + return WarnOnAndroidSdkFallbackVersion(); + } + + /// + /// Convert a BuildTarget to a BuildTargetGroup. + /// + /// Unfortunately the Unity API does not provide a way to get the current BuildTargetGroup from + /// the currently active BuildTarget. + /// BuildTarget to convert. + /// BuildTargetGroup enum value. + private static BuildTargetGroup ConvertBuildTargetToBuildTargetGroup(BuildTarget buildTarget) { + var buildTargetToGroup = new Dictionary() { + { "StandaloneOSXUniversal", "Standalone" }, + { "StandaloneOSXIntel", "Standalone" }, + { "StandaloneLinux", "Standalone" }, + { "StandaloneWindows64", "Standalone" }, + { "WSAPlayer", "WSA" }, + { "StandaloneLinux64", "Standalone" }, + { "StandaloneLinuxUniversal", "Standalone" }, + { "StandaloneOSXIntel64", "Standalone" }, + }; + string buildTargetString = buildTarget.ToString(); + string buildTargetGroupString; + if (!buildTargetToGroup.TryGetValue(buildTargetString, out buildTargetGroupString)) { + // If the conversion fails try performing a 1:1 mapping between the platform and group + // as most build targets only map to one group. + buildTargetGroupString = buildTargetString; + } + try { + return (BuildTargetGroup)Enum.Parse(typeof(BuildTargetGroup), buildTargetGroupString); + } catch (ArgumentException) { + return BuildTargetGroup.Unknown; + } + } + + /// + /// Get the bundle identifier for the active build target group *not* the selected build + /// target group using Unity 5.6 and above's API. + /// + /// + /// Build target to query. + /// Application identifier if it can be retrieved, null otherwise. + private static string GetUnity56AndAboveApplicationIdentifier(BuildTarget buildTarget) { + var getApplicationIdentifierMethod = + typeof(UnityEditor.PlayerSettings).GetMethod("GetApplicationIdentifier", new[]{typeof(BuildTargetGroup)}); + if (getApplicationIdentifierMethod == null) return null; + var buildTargetGroup = ConvertBuildTargetToBuildTargetGroup(buildTarget); + if (buildTargetGroup == BuildTargetGroup.Unknown) return null; + return getApplicationIdentifierMethod.Invoke( + null, new object [] { buildTargetGroup } ) as string; + } + + /// + /// Set the bundle identifier for the specified build target group in Unity 5.6 and above. + /// + /// + /// Build target to query. + /// Application ID to set. + /// true if successful, false otherwise. + private static bool SetUnity56AndAboveApplicationIdentifier(BuildTarget buildTarget, + string applicationIdentifier) { + var setApplicationIdentifierMethod = + 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; + setApplicationIdentifierMethod.Invoke( + null, new object [] { buildTargetGroup, applicationIdentifier } ); + return true; + } + + /// + /// Get the bundle identifier for the currently active build target using the Unity 5.5 and + /// below API. + /// + private static string Unity55AndBelowBundleIdentifier { + get { + var property = typeof(UnityEditor.PlayerSettings).GetProperty("bundleIdentifier"); + if (property == null) return null; + return (string)property.GetValue(null, null); + } + + set { + var property = typeof(UnityEditor.PlayerSettings).GetProperty("bundleIdentifier"); + if (property == null) return; + property.SetValue(null, value, null); + } + } + + /// + /// Get the bundle / application ID for a build target. + /// + /// Build target to query. + /// This is ignored in Unity 5.5 and below as all build targets share the same ID. + /// + /// Bundle / application ID for the build target. + public static string GetApplicationId(BuildTarget buildTarget) { + var identifier = GetUnity56AndAboveApplicationIdentifier(buildTarget); + if (identifier != null) return identifier; + return Unity55AndBelowBundleIdentifier; + } + + /// + /// Set the bundle / application ID for a build target. + /// + /// Build target to select. + /// This is ignored in Unity 5.5 and below as all build targets share the same ID. + /// + /// Bundle / application ID to set. + public static void SetApplicationId(BuildTarget buildTarget, string applicationIdentifier) { + if (!SetUnity56AndAboveApplicationIdentifier(buildTarget, applicationIdentifier)) { + Unity55AndBelowBundleIdentifier = applicationIdentifier; + } + } + + /// + /// Get / set the bundle / application ID. + /// + /// Unity 5.6 and above have the concept of an active build target and the selected build + /// target. The active build target is the target that is built when the user presses the + /// build button. The selected build target is the target that is currently selected in + /// the build settings dialog but not active to build (i.e no Unity icon is visible next to + /// the build target). + /// + /// This uses reflection to retrieve the property as it was renamed in Unity 5.6. + public static string ApplicationId { + get { return GetApplicationId(EditorUserBuildSettings.activeBuildTarget); } + set { SetApplicationId(EditorUserBuildSettings.activeBuildTarget, value); } + } +} +} diff --git a/source/AndroidResolver/src/VersionNumber.cs b/source/AndroidResolver/src/VersionNumber.cs new file mode 100644 index 00000000..5309b99f --- /dev/null +++ b/source/AndroidResolver/src/VersionNumber.cs @@ -0,0 +1,42 @@ +// +// 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 UnityEditor; + + /// + /// Get the version number of this plugin. + /// + public class AndroidResolverVersionNumber { + + /// + /// 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/AndroidResolver/src/XmlDependencies.cs b/source/AndroidResolver/src/XmlDependencies.cs new file mode 100644 index 00000000..24a02594 --- /dev/null +++ b/source/AndroidResolver/src/XmlDependencies.cs @@ -0,0 +1,98 @@ +// +// Copyright (C) 2017 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 UnityEditor; + + /// + /// Parses XML declared dependencies required by a Unity plugin. + /// + internal class XmlDependencies { + + /// + /// Set of regular expressions that match files which contain dependency + /// specifications. + /// + internal HashSet fileRegularExpressions = new HashSet { + new Regex(@".*[/\\]Editor[/\\].*Dependencies\.xml$") + }; + + /// + /// Human readable name for dependency files managed by this class. + /// + 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() { + return new List( + VersionHandlerImpl.SearchAssetDatabase( + "Dependencies t:TextAsset", IsDependenciesFile, + new [] { "Assets", "Packages"})); + } + + /// + /// Read XML declared dependencies. + /// + /// File to read. + /// Logger class. + /// true if the file was read successfully, false otherwise. + protected virtual bool Read(string filename, Logger logger) { + return false; + } + + /// + /// Find and read all XML declared dependencies. + /// + /// Logger class. + /// true if all files were read successfully, false otherwise. + public virtual bool ReadAll(Logger logger) { + bool success = true; + 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.", dependencyType, + filename), + level: LogLevel.Error); + success = false; + } + } + return success; + } + } +} 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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestAdditionalDependencies.template b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestAdditionalDependencies.template new file mode 100644 index 00000000..79d03c36 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestAdditionalDependencies.template @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file 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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestDependencies.xml b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestDependencies.xml new file mode 100644 index 00000000..d44ed8d4 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/ExternalDependencyManager/Editor/TestDependencies.xml @@ -0,0 +1,19 @@ + + + + + + + + Assets/Firebase/m2repository + + + + + + + file:///my/nonexistant/test/repo + project_relative_path/repo + + + diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100755 index 00000000..160e672a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/5.1.1/firebase-app-unity-5.1.1.pom @@ -0,0 +1,13 @@ + + 4.0.0 + com.google.firebase + firebase-app-unity + 5.1.1 + aar + + + + + diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100755 index 00000000..23794ccb Binary files /dev/null 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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100755 index 00000000..43dfbdf0 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Firebase/m2repository/com/google/firebase/firebase-app-unity/maven-metadata.xml @@ -0,0 +1,10 @@ + + com.google.firebase + firebase-app-unity + + 5.1.1 + 5.1.1 + + + + 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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/mainTemplateDISABLED.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/Assets/Plugins/Android/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/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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..f267458c Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.core.common-1.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..3f7a7d78 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.common-1.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..ceb329ff Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/android.arch.lifecycle.runtime-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..0821412a Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-annotations-26.1.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..bd37e17a Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-compat-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..34bfd64e Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-ui-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..7187884d Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-core-utils-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..3a8d9c66 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-fragment-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..352233e5 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-media-compat-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..4c936b37 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.android.support.support-v4-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..20a42f28 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..e10066cc Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/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 new file mode 100644 index 00000000..23794ccb Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..6e1b4a57 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/Gradle/com.google.firebase.firebase-common-16.0.0.aar differ 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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..f267458c Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.core.common-1.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..3f7a7d78 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.common-1.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..ceb329ff Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/android.arch.lifecycle.runtime-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..0821412a Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-annotations-26.1.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..bd37e17a Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-compat-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..34bfd64e Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-ui-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..7187884d Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-core-utils-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..3a8d9c66 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-fragment-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..352233e5 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-media-compat-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..4c936b37 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.android.support.support-v4-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..b1dbf35c Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-base-15.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..20a42f28 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-basement-15.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..e10066cc Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.android.gms.play-services-tasks-15.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/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 new file mode 100644 index 00000000..23794ccb Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..6e1b4a57 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/Export/GradleAddedDeps/com.google.firebase.firebase-common-16.0.0.aar differ 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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..f267458c Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.core.common-1.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..3f7a7d78 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.common-1.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..ceb329ff Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/android.arch.lifecycle.runtime-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..0821412a Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-annotations-26.1.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..bd37e17a Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-compat-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..34bfd64e Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-ui-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..7187884d Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-core-utils-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..3a8d9c66 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-fragment-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..352233e5 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-media-compat-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..4c936b37 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.android.support.support-v4-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..20a42f28 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-basement-15.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..e10066cc Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.android.gms.play-services-tasks-15.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/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 new file mode 100644 index 00000000..23794ccb Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-app-unity-5.1.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..5d44c876 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/Gradle/com.google.firebase.firebase-common-16.0.0.aar differ 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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..0821412a Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/com.android.support.support-annotations-26.1.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7a/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 new file mode 100644 index 00000000..23794ccb Binary files /dev/null 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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..0821412a Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleArmeabiv7aArm64/com.android.support.support-annotations-26.1.0.jar differ 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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplate.gradle new file mode 100644 index 00000000..5a09a8be --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/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/GradleTemplate/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/mainTemplateDISABLED.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplate/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/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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/mainTemplateDISABLED.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/mainTemplateDISABLED.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateDuplicatePackages/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/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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplate.gradle b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/mainTemplate.gradle new file mode 100644 index 00000000..5b7bb06a --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/GradleTemplateEmpty/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/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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..f267458c Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.core.common-1.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..3f7a7d78 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.common-1.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..ceb329ff Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/android.arch.lifecycle.runtime-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..0821412a Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-annotations-26.1.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..bd37e17a Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-compat-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..34bfd64e Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-ui-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..7187884d Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-core-utils-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..3a8d9c66 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-fragment-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..352233e5 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-media-compat-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..4c936b37 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.android.support.support-v4-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..20a42f28 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-basement-15.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..e10066cc Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.android.gms.play-services-tasks-15.0.1.aar differ 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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..b53feb36 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/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/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 new file mode 100644 index 00000000..d80f0c58 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/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/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 new file mode 100644 index 00000000..c0beb954 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/com.google.firebase.firebase-common-16.0.0/libs/classes.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..63c0883b --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/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/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 new file mode 100644 index 00000000..d28e5065 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/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/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 new file mode 100644 index 00000000..0f8c2436 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/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/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 new file mode 100644 index 00000000..7302c6e2 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAars/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/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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..f267458c Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.core.common-1.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..3f7a7d78 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.common-1.0.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..ceb329ff Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/android.arch.lifecycle.runtime-1.0.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..0821412a Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-annotations-26.1.0.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..bd37e17a Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-compat-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..34bfd64e Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-ui-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..7187884d Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-core-utils-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..3a8d9c66 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-fragment-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..352233e5 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-media-compat-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..4c936b37 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.android.support.support-v4-26.1.0.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..20a42f28 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-basement-15.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..e10066cc Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.android.gms.play-services-tasks-15.0.1.aar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..2449f75e --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + 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/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..e69de29b diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..18640013 Binary files /dev/null and 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 differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..15cb0ecb Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/classes.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-app-unity-5.1.1/libs/unsupported/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 new file mode 100644 index 00000000..e7c246c1 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/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/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 new file mode 100644 index 00000000..64171024 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/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/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 new file mode 100644 index 00000000..d28e5065 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/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/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 new file mode 100644 index 00000000..b53feb36 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/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/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 new file mode 100644 index 00000000..d80f0c58 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/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/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 new file mode 100644 index 00000000..c0beb954 Binary files /dev/null and b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/com.google.firebase.firebase-common-16.0.0/libs/classes.jar differ diff --git a/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/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 new file mode 100644 index 00000000..63c0883b --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/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/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 new file mode 100644 index 00000000..d28e5065 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/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/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 new file mode 100644 index 00000000..0f8c2436 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/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/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 new file mode 100644 index 00000000..7302c6e2 --- /dev/null +++ b/source/AndroidResolver/test/AndroidResolverIntegrationTestsUnityProject/ExpectedArtifacts/NoExport/InternalNativeAarsExploded/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/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/AndroidResolver/test/src/AndroidResolverIntegrationTests.cs b/source/AndroidResolver/test/src/AndroidResolverIntegrationTests.cs new file mode 100644 index 00000000..cc7a6e66 --- /dev/null +++ b/source/AndroidResolver/test/src/AndroidResolverIntegrationTests.cs @@ -0,0 +1,1106 @@ +// +// Copyright (C) 2018 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.IO; +using System.Linq; +using System.Reflection; + +using Google.JarResolver; +using GooglePlayServices; + +namespace Google { + +public class AndroidResolverIntegrationTests { + + /// + /// EditorUserBuildSettings property which controls the Android build system. + /// + private const string ANDROID_BUILD_SYSTEM = "androidBuildSystem"; + + /// + /// EditorUserBuildSettings property which controls whether an Android project is exported. + /// + private const string EXPORT_ANDROID_PROJECT = "exportAsGoogleAndroidProject"; + + /// + /// The name of the file, without extension, that will serve as a template for dynamically + /// adding additional dependencies. + /// + private const string ADDITIONAL_DEPENDENCIES_FILENAME = "TestAdditionalDependencies"; + + /// + /// 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 const string ADDITIONAL_DUPLICATE_DEPENDENCIES_FILENAME = "TestAdditionalDuplicateDependencies"; + + /// + /// Disabled application Gradle template file. + /// + private const string GRADLE_TEMPLATE_DISABLED = + "Assets/Plugins/Android/mainTemplateDISABLED.gradle"; + + /// + /// Disabled library Gradle template file. + /// + private const string GRADLE_TEMPLATE_LIBRARY_DISABLED = + "Assets/Plugins/Android/mainTemplateLibraryDISABLED.gradle"; + + /// + /// Disabled Gradle properties template file. + /// + private const string GRADLE_TEMPLATE_PROPERTIES_DISABLED = + "Assets/Plugins/Android/gradleTemplateDISABLED.properties"; + + /// + /// Disabled Gradle settings template file. + /// + 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"; + + /// + /// + /// Enabled Gradle template properties file. + /// + private const string GRADLE_TEMPLATE_PROPERTIES_ENABLED = "Assets/Plugins/Android/gradleTemplate.properties"; + + /// + /// + /// Enabled Gradle settings properties file. + /// + private const string GRADLE_TEMPLATE_SETTINGS_ENABLED = "Assets/Plugins/Android/settingsTemplate.gradle"; + + /// + /// Configure tests to run. + /// + [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_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."); + IntegrationTester.Runner.ScheduleTestCases(new [] { + // This *must* be the first test case as other test cases depend upon it. + new IntegrationTester.TestCase { + Name = "ValidateAndroidTargetSelected", + Method = ValidateAndroidTargetSelected, + }, + new IntegrationTester.TestCase { + Name = "SetupDependencies", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + SetupDependencies(); + + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + ValidateDependencies(testCaseResult); + testCaseComplete(testCaseResult); + } + }, + 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( + GRADLE_TEMPLATE_LIBRARY_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 = "ResolveForGradleBuildSystemWithTemplateEmpty", + Method = (testCase, testCaseComplete) => { + string enabledDependencies = + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml"; + string disabledDependencies = + "Assets/ExternalDependencyManager/Editor/TestDependenciesDISABLED.xml"; + Action enableDependencies = () => { + UnityEditor.AssetDatabase.MoveAsset(disabledDependencies, + enabledDependencies); + }; + try { + // Disable all XML dependencies. + var error = UnityEditor.AssetDatabase.MoveAsset(enabledDependencies, + disabledDependencies); + if (!String.IsNullOrEmpty(error)) { + 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 IntegrationTester.TestCase { + Name = "ResolveForGradleBuildSystem", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + SetupDependencies(); + Resolve("Gradle", false, "ExpectedArtifacts/NoExport/Gradle", + null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); + } + }, + new IntegrationTester.TestCase { + Name = "ResolveForGradleBuildSystemSync", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + SetupDependencies(); + Resolve("Gradle", false, "ExpectedArtifacts/NoExport/Gradle", + null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete, + synchronous: true); + } + }, + new IntegrationTester.TestCase { + Name = "ResolveForGradleBuildSystemAndExport", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + SetupDependencies(); + Resolve("Gradle", true, "ExpectedArtifacts/Export/Gradle", + null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); + } + }, + new IntegrationTester.TestCase { + Name = "ResolveAddedDependencies", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + SetupDependencies(); + UpdateAdditionalDependenciesFile(true); + Resolve("Gradle", true, "ExpectedArtifacts/Export/GradleAddedDeps", + null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); + } + }, + new IntegrationTester.TestCase { + Name = "ResolveRemovedDependencies", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + SetupDependencies(); + // Add the additional dependencies file then immediately remove it. + UpdateAdditionalDependenciesFile(true); + UpdateAdditionalDependenciesFile(false); + Resolve("Gradle", true, "ExpectedArtifacts/Export/Gradle", + null, nonGradleTemplateFilesToIgnore, testCase, testCaseComplete); + } + }, + new IntegrationTester.TestCase { + Name = "DeleteResolvedLibraries", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + SetupDependencies(); + Resolve("Gradle", true, "ExpectedArtifacts/Export/Gradle", + null, nonGradleTemplateFilesToIgnore, + testCase, (testCaseResult) => { + PlayServicesResolver.DeleteResolvedLibrariesSync(); + var unexpectedFilesMessage = new List(); + var resolvedFiles = ListFiles("Assets/Plugins/Android", + nonGradleTemplateFilesToIgnore); + if (resolvedFiles.Count > 0) { + unexpectedFilesMessage.Add("Libraries not deleted!"); + foreach (var filename in resolvedFiles.Values) { + unexpectedFilesMessage.Add(filename); + } + } + testCaseResult.ErrorMessages.AddRange(unexpectedFilesMessage); + testCaseComplete(testCaseResult); + }, + synchronous: true); + } + }, + 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( + GRADLE_TEMPLATE_DISABLED, + expectedAssetsDir, + testCase, (testCaseResult) => { + 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( + 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, + filesToIgnore: filesToIgnore, + deleteGradleTemplateSettings: false, + gradleTemplateSettings: gradleTemplateSettings); + } + }, + }); + + // 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(); + SetupDependencies(); + Resolve("Internal", false, AarsWithNativeLibrariesSupported ? + "ExpectedArtifacts/NoExport/InternalNativeAars" : + "ExpectedArtifacts/NoExport/InternalNativeAarsExploded", + null, nonGradleTemplateFilesToIgnore, testCase, + testCaseComplete); + } + }, + new IntegrationTester.TestCase { + Name = "ResolveForInternalBuildSystemUsingJetifier", + Method = (testCase, testCaseComplete) => { + ClearAllDependencies(); + SetupDependencies(); + GooglePlayServices.SettingsDialog.UseJetifier = true; + Resolve("Internal", false, AarsWithNativeLibrariesSupported ? + "ExpectedArtifacts/NoExport/InternalNativeAarsJetifier" : + "ExpectedArtifacts/NoExport/InternalNativeAarsExplodedJetifier", + null, nonGradleTemplateFilesToIgnore, testCase, + testCaseComplete); + } + }, + }); + } + + // 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 Current Unity Version requires Maven Repo in Gradle Settings Template. + /// + private static bool UnityChangeMavenInSettings_2022_2 { + get { return IntegrationTester.Runner.UnityVersion >= 2022.2f; } + } + + /// + /// Whether Current Unity Version requires Jetifier enabling in Gradle Properties Template. + /// + private static bool UnityChangeJetifierInProperties_2019_3 { + get { return IntegrationTester.Runner.UnityVersion >= 2019.3f; } + } + + /// + /// Whether the Gradle builds are supported by the current version of Unity. + /// + private static bool GradleBuildSupported { + get { return IntegrationTester.Runner.UnityVersion >= 5.5f; } + } + + /// + /// Whether the current version of Unity requires AARs with native artifacts to be converted + /// to ant / eclipse projects. + /// + private static bool AarsWithNativeLibrariesSupported { + get { return IntegrationTester.Runner.UnityVersion < 2017.0f; } + } + + /// + /// Get a property from UnityEditor.EditorUserBuildSettings. + /// + /// Properties are introduced over successive versions of Unity so use reflection to + /// retrieve them. + /// Property value. + private static object GetEditorUserBuildSettingsProperty(string name, + object defaultValue) { + var property = typeof(UnityEditor.EditorUserBuildSettings).GetProperty(name); + if (property != null) { + var value = property.GetValue(null, null); + if (value != null) return value; + } + return defaultValue; + } + + /// + /// Set a property on UnityEditor.EditorUserBuildSettings. + /// + /// true if set, false otherwise. + private static bool SetEditorUserBuildSettingsProperty(string name, object value) { + var property = typeof(UnityEditor.EditorUserBuildSettings).GetProperty(name); + if (property == null) return false; + try { + property.SetValue(null, value, null); + } catch (ArgumentException) { + return false; + } + return true; + } + + /// + /// Encode a string as a value of the AndroidBuildSystem enum type. + /// + private static object StringToAndroidBuildSystemValue(string value) { + var androidBuildSystemType = Google.VersionHandler.FindClass( + "UnityEditor", "UnityEditor.AndroidBuildSystem"); + if (androidBuildSystemType == null) return null; + return Enum.Parse(androidBuildSystemType, value); + } + + /// + /// Make sure the Android platform is selected for testing. + /// + private static void ValidateAndroidTargetSelected( + IntegrationTester.TestCase testCase, + Action testCaseComplete) { + if (UnityEditor.EditorUserBuildSettings.activeBuildTarget != + UnityEditor.BuildTarget.Android) { + IntegrationTester.Runner.LogTestCaseResult( + new IntegrationTester.TestCaseResult(testCase) { + ErrorMessages = new List() { "Target platform must be Android" } + }); + IntegrationTester.Runner.LogSummaryAndExit(); + } + + // Verify if PlayServicesResolver properties are working properly. + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + + if (String.IsNullOrEmpty(PlayServicesResolver.AndroidGradlePluginVersion)) { + testCaseResult.ErrorMessages.Add(String.Format( + "PlayServicesResolver.AndroidGradlePluginVersion is empty or null")); + } + + 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); + } + + /// + /// Clear *all* dependencies. + /// This removes all programmatically added dependencies before running a test. + /// A developer typically shouldn't be doing this, instead they should be changing the + /// *Dependencies.xml files in the project to force the dependencies to be read again. + /// This also removes the additional dependencies file. + /// + private static void ClearAllDependencies() { + UnityEngine.Debug.Log("Clear all loaded dependencies"); + GooglePlayServices.SettingsDialog.UseJetifier = false; + GooglePlayServices.SettingsDialog.PatchPropertiesTemplateGradle = false; + GooglePlayServices.SettingsDialog.PatchSettingsTemplateGradle = false; + + GooglePlayServices.SettingsDialog.UserRejectedGradleUpgrade = true; + + PlayServicesSupport.ResetDependencies(); + UpdateAdditionalDependenciesFile(false, ADDITIONAL_DEPENDENCIES_FILENAME); + UpdateAdditionalDependenciesFile(false, ADDITIONAL_DUPLICATE_DEPENDENCIES_FILENAME); + } + + /// + /// Programmatically add dependencies. + /// NOTE: This is the deprecated way of adding dependencies and will likely be removed in + /// future. + /// + private static void SetupDependencies() { + PlayServicesSupport.CreateInstance("Test", null, "ProjectSettings").DependOn( + "com.google.firebase", "firebase-common", "16.0.0"); + } + + /// + /// Validate Android libraries and repos are setup correctly. + /// + /// TestCaseResult instance to add errors to if this method + /// fails. + 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/ExternalDependencyManager/Editor/TestDependencies.xml:4"), + new KeyValuePair( + "com.google.firebase:firebase-app-unity:5.1.1", + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10"), + new KeyValuePair( + "com.google.firebase:firebase-common:16.0.0", + "Google.AndroidResolverIntegrationTests.SetupDependencies"), + new KeyValuePair( + "org.test.psr:classifier:1.0.1:foo@aar", + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:12"), + }, + PlayServicesResolver.GetPackageSpecs(), + "Package Specs", testCaseResult); + // Validate configured repos are present. + CompareKeyValuePairLists( + new List>() { + new KeyValuePair( + "file:///my/nonexistant/test/repo", + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17"), + new KeyValuePair( + "file:///" + Path.GetFullPath("project_relative_path/repo").Replace("\\", "/"), + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:17"), + new KeyValuePair( + "file:///" + Path.GetFullPath( + "Assets/Firebase/m2repository").Replace("\\", "/"), + "Assets/ExternalDependencyManager/Editor/TestDependencies.xml:10") + }, + PlayServicesResolver.GetRepos(), + "Repos", testCaseResult); + } + + /// + /// Compare two ordered lists. + /// + /// Expected list. + /// List to compare with expectedList. + /// Human readable description of both lists. + /// TestCaseResult instance to add errors to if lists do not + /// match. + private static void CompareKeyValuePairLists( + IList> expectedList, + IList> testList, string listDescription, + IntegrationTester.TestCaseResult testCaseResult) { + if (expectedList.Count != testList.Count) { + testCaseResult.ErrorMessages.Add(String.Format( + "Returned list of {0} is an unexpected size {1} vs {2}", + listDescription, testList.Count, expectedList.Count)); + return; + } + for (int i = 0; i < expectedList.Count; ++i) { + var expected = expectedList[i]; + var test = testList[i]; + if (expected.Key != test.Key || expected.Value != test.Value) { + testCaseResult.ErrorMessages.Add(String.Format( + "Element {0} of list {1} ({2} {3}) mismatches the expected value ({4} {5})", + i, listDescription, test.Key, test.Value, expected.Key, expected.Value)); + } + } + } + + /// + /// Programmatically add/remove dependencies by copying/deleting a template file. + /// The change will be processed by the plugin after the UnityEditor.AssetDatabase.Refresh() + /// call. + /// + /// 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. + /// 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/ExternalDependencyManager/Editor/"); + + string templateFilePath = Path.Combine(editorPath, filename+ + ".template"); + string xmlFilePath = Path.Combine(editorPath, filename+ ".xml"); + if (addDependencyFile && !File.Exists(xmlFilePath)) { + if (!File.Exists(templateFilePath)) { + UnityEngine.Debug.LogError("Could not find file: " + templateFilePath); + return; + } + + UnityEngine.Debug.Log("Adding Dependencies file: " + xmlFilePath); + File.Copy(templateFilePath, xmlFilePath); + UnityEditor.AssetDatabase.Refresh(); + } else if (!addDependencyFile && File.Exists(xmlFilePath)) { + UnityEngine.Debug.Log("Removing Dependencies file: " + xmlFilePath); + File.Delete(xmlFilePath); + File.Delete(xmlFilePath + ".meta"); + UnityEditor.AssetDatabase.Refresh(); + } + } + + /// + /// Asynchronously run the Android Resolver and validate the result with + /// ValidateAndroidResolution. + /// + /// Android build system to select. + /// Whether Android project export should be enabled. + /// Directory that contains the assets expected from the + /// resolution step. + /// String of Android ABIs to target or null if the default ABIs + /// should be selected. + /// Set of files to relative to the generatedAssetsDir. + /// Object executing this method. + /// Called with the test result. + /// Whether the resolution should be executed synchronously. + private static void Resolve(string androidBuildSystem, bool exportProject, + string expectedAssetsDir, string targetAbis, + ICollection filesToIgnore, + IntegrationTester.TestCase testCase, + Action testCaseComplete, + bool synchronous = false) { + // Set the Android target ABIs. + GooglePlayServices.AndroidAbis.CurrentString = targetAbis; + // Try setting the build system if this version of Unity supports it. + if (!GradleBuildSupported && androidBuildSystem == "Gradle") { + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { + Skipped = true, + ErrorMessages = new List { + "Unity version does not support Gradle builds." + } + }); + return; + } + if (!(SetEditorUserBuildSettingsProperty( + ANDROID_BUILD_SYSTEM, StringToAndroidBuildSystemValue(androidBuildSystem)) && + GetEditorUserBuildSettingsProperty( + ANDROID_BUILD_SYSTEM, androidBuildSystem).ToString() == androidBuildSystem)) { + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { + ErrorMessages = new List { + String.Format("Unable to set AndroidBuildSystem to {0}.", + androidBuildSystem) + } + }); + return; + } + // Configure project export setting. + if (!(SetEditorUserBuildSettingsProperty(EXPORT_ANDROID_PROJECT, exportProject) && + (bool)GetEditorUserBuildSettingsProperty(EXPORT_ANDROID_PROJECT, + exportProject) == exportProject)) { + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { + ErrorMessages = new List { + String.Format("Unable to set Android export project to {0}.", + exportProject) + } + }); + } + + // Resolve dependencies. + Action completeWithResult = (bool complete) => { + IntegrationTester.Runner.ExecuteTestCase( + testCase, + () => { + testCaseComplete(new IntegrationTester.TestCaseResult(testCase) { + ErrorMessages = ValidateAndroidResolution(expectedAssetsDir, complete, + filesToIgnore) + }); + }, true); + }; + if (synchronous) { + bool success = PlayServicesResolver.ResolveSync(true); + completeWithResult(success); + } else { + 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. + /// Called with the test result. + /// Set of additional files that are expected in the + /// project. + /// Whether to delete the gradle template before + /// testCaseComplete is called. + /// 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) { + if (File.Exists(filename)) File.Delete(filename); + } + }; + try { + 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)) { + testCaseResult.ErrorMessages.Add(String.Format("{0} not found", + expectedFile)); + } + } + } + cleanUpTestCase(); + testCaseComplete(testCaseResult); + }, synchronous: true); + } catch (Exception ex) { + var testCaseResult = new IntegrationTester.TestCaseResult(testCase); + testCaseResult.ErrorMessages.Add(ex.ToString()); + cleanUpTestCase(); + testCaseComplete(testCaseResult); + } + } + + /// + /// Get a list of files under a directory indexed by the path relative to the directory. + /// 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. + private static Dictionary ListFiles(string searchDir, + ICollection filesToIgnore, + string relativeDir = null) { + var foundFiles = new Dictionary(); + relativeDir = relativeDir != null ? relativeDir : searchDir; + foreach (var path in Directory.GetFiles(searchDir)) { + var relativeFilename = path.Substring(relativeDir.Length + 1); + // Skip files that should be ignored. + if (path.EndsWith(".meta") || + (filesToIgnore != null && filesToIgnore.Contains(relativeFilename))) { + continue; + } + foundFiles[relativeFilename] = path; + } + foreach (var path in Directory.GetDirectories(searchDir)) { + foreach (var kv in ListFiles(path, filesToIgnore, relativeDir)) { + foundFiles[kv.Key] = kv.Value; + } + } + return foundFiles; + } + + /// + /// Extract a zip file. + /// + /// File to extract. + /// List to add any failure messages to. + /// Directory containing unzipped files if successful, null otherwise. + private static string ExtractZip(string zipFile, List failureMessages) { + string outputDir = Path.Combine(Path.Combine(Path.GetTempPath(), + Path.GetRandomFileName()), + Path.GetFileName(zipFile)); + Directory.CreateDirectory(outputDir); + // This uses reflection to access an internal method for testing purposes. + // ExtractZip is not part of the public API. + bool successful = PlayServicesResolver.ExtractZip(zipFile, null, outputDir, false); + if (!successful) { + failureMessages.Add(String.Format("Unable to extract {0} to {1}", + zipFile, outputDir)); + Directory.Delete(outputDir, true); + return null; + } + return outputDir; + } + + /// + /// Compare the contents of two directories. + /// + /// Directory that contains expected assets. + /// Directory that contains generated assets. + /// Set of files to relative to the generatedAssetsDir. + /// List of errors. If validation was successful the list will be empty. + private static List CompareDirectoryContents(string expectedAssetsDir, + string generatedAssetsDir, + ICollection filesToIgnore) { + var failureMessages = new List(); + // Get the set of expected artifact paths and resolved artifact paths. + var expectedAndResolvedArtifactsByFilename = + new Dictionary>(); + foreach (var kv in ListFiles(expectedAssetsDir, null)) { + expectedAndResolvedArtifactsByFilename[kv.Key] = + new KeyValuePair(kv.Value, null); + } + foreach (var kv in ListFiles(generatedAssetsDir, filesToIgnore)) { + KeyValuePair expectedResolved; + if (expectedAndResolvedArtifactsByFilename.TryGetValue(kv.Key, + out expectedResolved)) { + expectedAndResolvedArtifactsByFilename[kv.Key] = + new KeyValuePair(expectedResolved.Key, kv.Value); + } else { + failureMessages.Add(String.Format("Found unexpected artifact {0}", kv.Value)); + } + } + // Report all missing files. + foreach (var kv in expectedAndResolvedArtifactsByFilename) { + var expectedResolved = kv.Value; + if (expectedResolved.Value == null) { + failureMessages.Add(String.Format("Missing expected artifact {0}", kv.Key)); + } + } + + // Compare contents of all expected and resolved files. + foreach (var expectedResolved in expectedAndResolvedArtifactsByFilename.Values) { + var expectedFile = expectedResolved.Key; + var resolvedFile = expectedResolved.Value; + if (resolvedFile == null) continue; + // If zip (jar / aar) files are recompacted they will differ due to change in timestamps + // and file ordering, so extract them and compare the results. + bool isZipFile = false; + foreach (var extension in new [] { ".aar", ".jar" }) { + if (expectedFile.EndsWith(extension)) { + isZipFile = true; + break; + } + } + var expectedContents = File.ReadAllBytes(expectedFile); + var resolvedContents = File.ReadAllBytes(resolvedFile); + if (!expectedContents.SequenceEqual(resolvedContents)) { + if (isZipFile) { + // Extract both files and compare the contents. + string[] extractedDirectories = new string[] { null, null }; + try { + var expectedDir = ExtractZip(expectedFile, failureMessages); + extractedDirectories[0] = expectedDir; + var resolvedDir = ExtractZip(resolvedFile, failureMessages); + extractedDirectories[1] = resolvedDir; + if (expectedDir != null && resolvedDir != null) { + var zipDirCompareFailures = CompareDirectoryContents(expectedDir, + resolvedDir, null); + if (zipDirCompareFailures.Count > 0) { + failureMessages.Add(String.Format("Artifact {0} does not match {1}", + resolvedFile, expectedFile)); + failureMessages.AddRange(zipDirCompareFailures); + } + } + } finally { + foreach (var directory in extractedDirectories) { + if (directory != null) Directory.Delete(directory, true); + } + } + } else { + bool differs = true; + // Determine whether to display the file as a string. + bool displayContents = false; + string expectedContentsAsString = "(binary)"; + string resolvedContentsAsString = expectedContentsAsString; + string resolvedExtension = Path.GetExtension(resolvedFile).ToLower(); + foreach (var extension in new[] { ".xml", ".txt", ".gradle", ".properties" }) { + if (resolvedExtension == extension) { + displayContents = true; + break; + } + } + if (displayContents) { + // 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) { + // Log an error. + failureMessages.Add(String.Format( + "Artifact {0} does not match contents of {1}\n" + + "--- {0} -------\n" + + "{2}\n" + + "--- {0} end ---\n" + + "--- {1} -------\n" + + "{3}\n" + + "--- {1} -------\n", + resolvedFile, expectedFile, resolvedContentsAsString, + expectedContentsAsString)); + } + } + } + } + return failureMessages; + } + + /// + /// Called when android dependency resolution is complete. + /// + /// Directory that contains the assets expected from the + /// resolution step. + /// true if resolution completed successfully, false otherwise. + /// Set of files to relative to the generatedAssetsDir. + /// List of errors. If validation was successful the list will be empty. + private static List ValidateAndroidResolution(string expectedAssetsDir, bool result, + ICollection filesToIgnore) { + var failureMessages = new List(); + if (!result) { + failureMessages.Add(String.Format("Android resolver reported a failure {0}", result)); + } + failureMessages.AddRange(CompareDirectoryContents(expectedAssetsDir, + "Assets/Plugins/Android", filesToIgnore)); + 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/AndroidResolver/unit_tests/Assets/AndroidResolverTests/src/Google.JarResolver.Tests/DependencyTests.cs b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/src/Google.JarResolver.Tests/DependencyTests.cs new file mode 100644 index 00000000..240686b7 --- /dev/null +++ b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/src/Google.JarResolver.Tests/DependencyTests.cs @@ -0,0 +1,74 @@ +// +// 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.JarResolvers.Test { + using Google.JarResolver; + using NUnit.Framework; + using System.Collections.Generic; + + /// + /// Dependency tests. + /// + [TestFixture] + public class DependencyTests { + /// + /// Tests an initialized dependency. + /// + [Test] + public void TestConstructor() { + Dependency dep = new Dependency("test", "artifact1", "1.0", + packageIds: new [] { "tools" }, + repositories: new [] { "a/repo/path" }, + createdBy: "someone"); + Assert.That(dep.CreatedBy, Is.EqualTo("someone")); + Assert.That(dep.Group, Is.EqualTo("test")); + Assert.That(dep.Artifact, Is.EqualTo("artifact1")); + Assert.That(dep.PackageIds, Is.EqualTo(new [] { "tools" })); + Assert.That(dep.Repositories, Is.EqualTo(new [] { "a/repo/path" })); + Assert.That(dep.VersionlessKey, Is.EqualTo("test:artifact1")); + Assert.That(dep.Key, Is.EqualTo("test:artifact1:1.0")); + Assert.That(dep.ToString(), Is.EqualTo("test:artifact1:1.0")); + } + + /// + /// Test version string comparison by sorting a list of versions. + /// + [Test] + public void TestSortVersionStrings() { + List sorted = new List { + "3.2.1", + "1.1", + "1.0.0+", + "1.2.0", + "1.3.a+", + "10", + "1.1.0", + "3.2.2", + "1.3.b", + }; + sorted.Sort(Dependency.versionComparer); + Assert.That(sorted[0], Is.EqualTo("10")); + Assert.That(sorted[1], Is.EqualTo("3.2.2")); + Assert.That(sorted[2], Is.EqualTo("3.2.1")); + Assert.That(sorted[3], Is.EqualTo("1.3.b")); + Assert.That(sorted[4], Is.EqualTo("1.3.a+")); + Assert.That(sorted[5], Is.EqualTo("1.2.0")); + Assert.That(sorted[6], Is.EqualTo("1.1.0")); + Assert.That(sorted[7], Is.EqualTo("1.1")); + Assert.That(sorted[8], Is.EqualTo("1.0.0+")); + } + } +} diff --git a/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/src/Google.JarResolver.Tests/PlayServicesSupportTests.cs b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/src/Google.JarResolver.Tests/PlayServicesSupportTests.cs new file mode 100644 index 00000000..f831b057 --- /dev/null +++ b/source/AndroidResolver/unit_tests/Assets/AndroidResolverTests/src/Google.JarResolver.Tests/PlayServicesSupportTests.cs @@ -0,0 +1,144 @@ +// +// 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.Editor.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using Google.JarResolver; + using NUnit.Framework; + + /// + /// Test data and methods to create it. + /// + internal static class TestData { + // Whether to display verbose log messages. + public const bool VERBOSE_LOGGING = false; + + // Path to test data, contains a mock SDK and maven repo. + public const string PATH = "../../testData"; + + // Stores expected data for Maven artifacts in TestData.PATH. + public class PackageInfo { + public string group; + public string artifact; + public string bestVersion; + }; + + // Maven artifacts available in TestData.PATH. + public enum PackageId { + Artifact, + TransDep, + SubDep, + }; + + // Properties of Maven artifacts in TestData.PATH. + public static Dictionary mavenArtifacts = + new Dictionary { + { + PackageId.Artifact, + new PackageInfo { + group = "test", + artifact = "artifact", + bestVersion = "8.2.0-alpha" + } + }, + { + PackageId.TransDep, + new PackageInfo { + group = "test", + artifact = "transdep", + bestVersion = "1.0.0", + } + }, + { + PackageId.SubDep, + new PackageInfo { + group = "test", + artifact = "subdep", + bestVersion = "0.9", + } + } + }; + + /// + /// Extension method of PackageId that returns the associated + /// PackageInfo instance. + /// + internal static PackageInfo Info(this PackageId artifactId) + { + return mavenArtifacts[artifactId]; + } + + /// + /// Extension method of PackageId that returns a version less key for the artifact + /// in the form "group:artifact". + /// + internal static string VersionlessKey(this PackageId artifactId) + { + var info = artifactId.Info(); + return String.Format("{0}:{1}", info.group, info.artifact); + } + + /// + /// Create a PlayServicesSupport instance for testing. + /// + public static PlayServicesSupport CreateInstance( + string instanceName = null, string sdkPath = null, + string[] additionalRepositories = null, PlayServicesSupport.LogMessage logger = null) + { + var instance = PlayServicesSupport.CreateInstance( + instanceName ?? "testInstance", sdkPath ?? PATH, + additionalRepositories, Path.GetTempPath(), logger: logger ?? Console.WriteLine); + PlayServicesSupport.verboseLogging = VERBOSE_LOGGING; + return instance; + } + + /// + /// Extension method for PlayServicesSupport that calls DependOn with the specified + /// artifact ID. + /// + internal static void DependOn(this PlayServicesSupport instance, PackageId artifactId, + string versionSpecifier) + { + var info = artifactId.Info(); + instance.DependOn(info.group, info.artifact, versionSpecifier); + } + } + + /// + /// Play services support tests. + /// + [TestFixture] + public class PlayServicesSupportTests + { + /// + /// Verify the logger delegate is called by the Log() method. + /// + [Test] + public void TestLogger() + { + List messageList = new List(); + string logMessage = "this is a test"; + PlayServicesSupport.logger = (message, level) => messageList.Add(message); + Assert.AreEqual(0, messageList.Count); + PlayServicesSupport.Log(logMessage); + Assert.AreEqual(1, messageList.Count); + Assert.AreEqual(logMessage, messageList[0]); + } + } +} 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/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor.meta new file mode 100644 index 00000000..457843c6 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: e105e00cdce8456482d26b1fcd1ca47d +folderAsset: yes +timeCreated: 1448926516 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: 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/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll.meta new file mode 100644 index 00000000..367d2e9e --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.IOSResolver_v1.2.87.0.dll.meta @@ -0,0 +1,34 @@ +fileFormatVersion: 2 +guid: 5889e1be319e46899d75db5bdf372fb9 +labels: +- gvh_v1.2.87.0 +- gvh +- gvh_teditor +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/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/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll.meta new file mode 100644 index 00000000..9a357d5b --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.JarResolver_v1.2.87.0.dll.meta @@ -0,0 +1,34 @@ +fileFormatVersion: 2 +guid: 380e8ddf4f4a46bfa31759d4ad1e82bc +labels: +- gvh_v1.2.87.0 +- gvh +- gvh_teditor +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/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/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta new file mode 100644 index 00000000..43e26f6a --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandler.dll.meta @@ -0,0 +1,34 @@ +fileFormatVersion: 2 +guid: 06f6f385a4ad409884857500a3c04441 +labels: +- gvh_v1.2.86.0 +- gvh +- gvh_teditor +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/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/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll.meta new file mode 100644 index 00000000..fc4128d4 --- /dev/null +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/Google.VersionHandlerImpl_v1.2.87.0.dll.meta @@ -0,0 +1,34 @@ +fileFormatVersion: 2 +guid: dc0556b86e9e4751a1553508cd89142f +labels: +- gvh_v1.2.87.0 +- gvh +- gvh_teditor +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/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/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.11.0.txt.meta b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt.meta similarity index 75% rename from exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.11.0.txt.meta rename to source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt.meta index 3e2058cd..4b034385 100644 --- a/exploded/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.11.0.txt.meta +++ b/source/ExportUnityPackage/test_data/Assets/PlayServicesResolver/Editor/play-services-resolver_v1.2.87.0.txt.meta @@ -1,6 +1,7 @@ fileFormatVersion: 2 -guid: be5fee9360b8466f9a876d7250139318 +guid: 4a1d3acb6c7545ffbd0b6601e3f57aee labels: +- gvh_v1.2.87.0 - gvh - gvh_manifest timeCreated: 1474401009 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 ccafa737..e16e2abf 100644 --- a/source/IOSResolver/IOSResolver.csproj +++ b/source/IOSResolver/IOSResolver.csproj @@ -23,7 +23,8 @@ False - none + True + full True bin\Release DEBUG;UNITY_EDITOR;UNITY_IOS @@ -50,7 +51,13 @@ - ..\PlayServicesResolver\bin\Release\Google.JarResolver.dll + ..\AndroidResolver\bin\Release\Google.JarResolver.dll + + + ..\VersionHandler\bin\Release\Google.VersionHandler.dll + + + ..\VersionHandler\bin\Release\Google.VersionHandlerImpl.dll @@ -60,12 +67,22 @@ + + - + {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A} - PlayServicesResolver + AndroidResolver + + + {5378B37A-887E-49ED-A8AE-42FA843AA9DC} + VersionHandler + + + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + VersionHandlerImpl diff --git a/source/IOSResolver/src/IOSResolver.cs b/source/IOSResolver/src/IOSResolver.cs index 31e7fe98..a272810e 100644 --- a/source/IOSResolver/src/IOSResolver.cs +++ b/source/IOSResolver/src/IOSResolver.cs @@ -15,21 +15,26 @@ // #if UNITY_IOS +using Google; using GooglePlayServices; using Google.JarResolver; using System; using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using System.Text.RegularExpressions; +using System.Threading; +using System.Xml; using UnityEditor; using UnityEditor.Callbacks; using UnityEngine; namespace Google { -public static class IOSResolver { +[InitializeOnLoad] +public class IOSResolver : AssetPostprocessor { /// /// Reference to a Cocoapod. /// @@ -40,14 +45,19 @@ private class Pod { public string name = null; /// - /// Version specification string. - /// If it ends with "+" the specified version up to the next major - /// version is selected. - /// If "LATEST", null or empty this pulls the latest revision. - /// A version number "1.2.3" selects a specific version number. + /// This is a preformatted version expression for pod declarations. + /// + /// See: https://guides.cocoapods.org/syntax/podfile.html#pod /// public string version = null; + /// + /// Properties applied to the pod declaration. + /// + /// See: + /// + public Dictionary propertiesByName = new Dictionary(); + /// /// Whether this pod has been compiled with bitcode enabled. /// @@ -56,29 +66,95 @@ private class Pod { /// public bool bitcodeEnabled = true; + /// + /// Additional sources (repositories) to search for this 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. + /// + /// See: https://guides.cocoapods.org/syntax/podfile.html#source + /// + public List sources = new List() { + "https://cdn.cocoapods.org/" + }; + /// /// Minimum target SDK revision required by this pod. /// In the form major.minor /// 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. + /// + public string createdBy = System.Environment.StackTrace; + + /// + /// Whether this pod was read from an XML dependencies file. + /// + public bool fromXmlFile = false; + + /// + /// Global set of sources for all pods. + /// + public static List> Sources = + new List>(); + + /// + /// Convert a dictionary of property key / value pairs to a string that can be appended + /// to a Podfile pod declaration. + /// + public static string PropertyDictionaryToString( + Dictionary propertiesByName) { + if (propertiesByName == null) return ""; + var propertyNamesAndValues = new List(); + foreach (var propertyItem in propertiesByName) { + propertyNamesAndValues.Add(String.Format(":{0} => {1}", propertyItem.Key, + propertyItem.Value)); + } + return String.Join(", ", propertyNamesAndValues.ToArray()); + } + + /// + /// Get the path of a pod without quotes. If the path isn't present, returns an empty + /// string. + /// + public string LocalPath { + get { + string path; + if (!propertiesByName.TryGetValue("path", out path)) return ""; + if (path.StartsWith("'") && path.EndsWith("'")) { + path = path.Substring(1, path.Length - 2); + } + return path; + } + } + + /// /// Format a "pod" line for a Podfile. /// public string PodFilePodLine { get { - string versionExpression = ""; - if (!String.IsNullOrEmpty(version) && - !version.Equals("LATEST")) { - if (version.EndsWith("+")) { - versionExpression = String.Format( - ", '~> {0}'", - version.Substring(0, version.Length - 1)); - } else { - versionExpression = String.Format(", '{0}'", version); - } + string podLine = String.Format("pod '{0}'", name); + if (!String.IsNullOrEmpty(version)) podLine += String.Format(", '{0}'", version); + + var outputPropertiesByName = new Dictionary(propertiesByName); + var path = LocalPath; + if (!String.IsNullOrEmpty(path)) { + outputPropertiesByName["path"] = String.Format("'{0}'", Path.GetFullPath(path)); } - return String.Format("pod '{0}'{1}", name, versionExpression); + var propertiesString = PropertyDictionaryToString(outputPropertiesByName); + if (!String.IsNullOrEmpty(propertiesString)) podLine += ", " + propertiesString; + + return podLine; } } @@ -91,19 +167,37 @@ public string PodFilePodLine { /// bitcode. /// Minimum target SDK revision required by /// this pod. - public Pod(string name, string version, bool bitcodeEnabled, - string minTargetSdk) { + /// 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. + /// Dictionary of additional properties for the pod + /// reference. + public Pod(string name, string version, bool bitcodeEnabled, string minTargetSdk, + bool addToAllTargets, IEnumerable sources, + Dictionary propertiesByName) { this.name = name; this.version = version; + if (propertiesByName != null) { + this.propertiesByName = new Dictionary(propertiesByName); + } this.bitcodeEnabled = bitcodeEnabled; this.minTargetSdk = minTargetSdk; + this.addToAllTargets = addToAllTargets; + if (sources != null) { + var allSources = new List(sources); + allSources.AddRange(this.sources); + this.sources = allSources; + } } /// /// 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; @@ -113,6 +207,35 @@ public int MinTargetSdkToVersion() { return IOSResolver.TargetSdkStringToVersion(sdkString); } + /// + /// Compare with this object. + /// This only compares values that can be encoded into a pod line in a Podfile. + /// + /// Object to compare with. + /// true if both objects have the same contents, false otherwise. + public override bool Equals(System.Object obj) { + var pod = obj as Pod; + return pod != null && + name == pod.name && + version == pod.version && + propertiesByName.Count == pod.propertiesByName.Count && + propertiesByName.Keys.All(key => + pod.propertiesByName.ContainsKey(key) && + propertiesByName[key] == pod.propertiesByName[key]); + } + + /// + /// Generate a hash of this object. + /// + /// Hash of this object. + public override int GetHashCode() { + int hash = 0; + if (name != null) hash ^= name.GetHashCode(); + if (version != null) hash ^= version.GetHashCode(); + foreach (var item in propertiesByName) hash ^= item.GetHashCode(); + return hash; + } + /// /// Given a list of pods bucket them into a dictionary sorted by /// min SDK version. Pods which specify no minimum version (e.g 0) @@ -140,22 +263,181 @@ public static SortedDictionary> } } + private class IOSXmlDependencies : XmlDependencies { + + // Properties to parse from a XML pod specification and store in the propert1iesByName + // dictionary of the Pod class. These are eventually expanded to the named arguments of the + // pod declaration in a Podfile. + // The value of each attribute with the exception of "path" is included as-is. + // "path" is converted to a full path on the local filesystem when the Podfile is generated. + private static string[] PODFILE_POD_PROPERTIES = new string[] { + "configurations", + "configuration", + "modular_headers", + "source", + "subspecs", + "path" + }; + + public IOSXmlDependencies() { + dependencyType = "iOS dependencies"; + } + + /// + /// Read XML declared dependencies. + /// + /// File to read. + /// Logger to log with. + /// + /// Parses dependencies in the form: + /// + /// + /// + /// + /// + /// uriToPodSource + /// + /// + /// + /// + protected override bool Read(string filename, Logger logger) { + IOSResolver.Log(String.Format("Reading iOS dependency XML file {0}", filename), + verbose: true); + var sources = new List(); + var trueStrings = new HashSet { "true", "1" }; + var falseStrings = new HashSet { "false", "0" }; + string podName = null; + string versionSpec = null; + bool bitcodeEnabled = true; + string minTargetSdk = null; + bool addToAllTargets = false; + var propertiesByName = new Dictionary(); + if (!XmlUtilities.ParseXmlTextFileElements( + filename, logger, + (reader, elementName, isStart, parentElementName, elementNameStack) => { + if (elementName == "dependencies" && parentElementName == "") { + return true; + } else if (elementName == "iosPods" && + (parentElementName == "dependencies" || + parentElementName == "")) { + return true; + } else if (elementName == "iosPod" && + parentElementName == "iosPods") { + if (isStart) { + podName = reader.GetAttribute("name"); + propertiesByName = new Dictionary(); + foreach (var propertyName in PODFILE_POD_PROPERTIES) { + string propertyValue = reader.GetAttribute(propertyName); + if (!String.IsNullOrEmpty(propertyValue)) { + propertiesByName[propertyName] = propertyValue; + } + } + versionSpec = reader.GetAttribute("version"); + var bitcodeEnabledString = + (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( + String.Format("Pod name not specified while reading {0}:{1}\n", + filename, reader.LineNumber), + level: LogLevel.Warning); + return false; + } + } else { + AddPodInternal(podName, preformattedVersion: versionSpec, + bitcodeEnabled: bitcodeEnabled, + minTargetSdk: minTargetSdk, + addToAllTargets: addToAllTargets, + sources: sources, + overwriteExistingPod: false, + createdBy: String.Format("{0}:{1}", + filename, reader.LineNumber), + fromXmlFile: true, + propertiesByName: propertiesByName); + } + return true; + } else if (elementName == "sources" && + parentElementName == "iosPod") { + return true; + } else if (elementName == "sources" && + parentElementName == "iosPods") { + if (isStart) { + sources = new List(); + } else { + foreach (var source in sources) { + Pod.Sources.Add( + new KeyValuePair( + source, String.Format("{0}:{1}", filename, + reader.LineNumber))); + } + } + return true; + } else if (elementName == "source" && + parentElementName == "sources") { + if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) { + sources.Add(reader.ReadContentAsString()); + } + return true; + } + // Ignore unknown tags so that different configurations can be stored in the + // same file. + return true; + })) { + return false; + } + return true; + } + } + // Dictionary of pods to install in the generated Xcode project. private static SortedDictionary pods = new SortedDictionary(); // Order of post processing operations. - private const int BUILD_ORDER_PATCH_PROJECT = 1; - private const int BUILD_ORDER_GEN_PODFILE = 2; - private const int BUILD_ORDER_INSTALL_PODS = 3; - private const int BUILD_ORDER_UPDATE_DEPS = 4; - - // Installation instructions for the Cocoapods command line tool. + 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". + private const string UNITY_PODFILE_BACKUP_POSTFIX = "_Unity.backup"; + + // Installation instructions for the CocoaPods command line tool. private const string COCOAPOD_INSTALL_INSTRUCTIONS = ( - "You can install cocoapods with the Ruby gem package manager:\n" + + "You can install CocoaPods with the Ruby gem package manager:\n" + " > 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 @@ -163,18 +445,10 @@ public static SortedDictionary> private static string[] POD_SEARCH_PATHS = new string[] { "/usr/local/bin", "/usr/bin", + "/opt/homebrew/bin", }; - - // Extensions of pod source files to include in the project. - private static HashSet SOURCE_FILE_EXTENSIONS = new HashSet( - new string[] { - ".h", - ".c", - ".cc", - ".cpp", - ".mm", - ".m", - }); + // Ruby Gem executable filename. + private static string GEM_EXECUTABLE = "gem"; /// /// Name of the Xcode project generated by Unity. @@ -182,38 +456,170 @@ public static SortedDictionary> 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. - private const string PREFERENCE_ENABLED = "Google.IOSResolver.Enabled"; - - /// - /// Whether verbose logging is enabled. - /// - internal static bool verboseLogging = false; + private const string PREFERENCE_NAMESPACE = "Google.IOSResolver."; + // Whether Legacy Cocoapod installation (project level) is enabled. + private const string PREFERENCE_COCOAPODS_INSTALL_ENABLED = PREFERENCE_NAMESPACE + "Enabled"; + // Whether Cocoapod uses project files, workspace files, or none (Unity 5.6+ only) + private const string PREFERENCE_COCOAPODS_INTEGRATION_METHOD = + PREFERENCE_NAMESPACE + "CocoapodsIntegrationMethod"; + // Whether the Podfile generation is enabled. + private const string PREFERENCE_PODFILE_GENERATION_ENABLED = + PREFERENCE_NAMESPACE + "PodfileEnabled"; + // Whether verbose logging is enabled. + private const string PREFERENCE_VERBOSE_LOGGING_ENABLED = + PREFERENCE_NAMESPACE + "VerboseLoggingEnabled"; + // 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"; + // A nag prompt disabler setting for turning on workspace integration. + private const string PREFERENCE_WARN_UPGRADE_WORKSPACE = + PREFERENCE_NAMESPACE + "UpgradeToWorkspaceWarningDisabled"; + // 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, + PREFERENCE_COCOAPODS_INTEGRATION_METHOD, + PREFERENCE_PODFILE_GENERATION_ENABLED, + PREFERENCE_VERBOSE_LOGGING_ENABLED, + 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_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. private static bool iOSXcodeExtensionLoaded = true; + // Whether a functioning Cocoapods install is present. + private static bool cocoapodsToolsInstallPresent = false; private static string IOS_PLAYBACK_ENGINES_PATH = Path.Combine("PlaybackEngines", "iOSSupport"); - // Directory containing downloaded Cocoapods relative to the project + // Directory containing downloaded CocoaPods relative to the project // directory. private const string PODS_DIR = "Pods"; - // Name of the project within PODS_DIR that references downloaded Cocoapods. + // Name of the project within PODS_DIR that references downloaded CocoaPods. private const string PODS_PROJECT_NAME = "Pods"; - - // Version of the Cocoapods installation. + // Prefix for static library filenames. + private const string LIBRARY_FILENAME_PREFIX = "lib"; + // Extension for static library filenames. + private const string LIBRARY_FILENAME_EXTENSION = ".a"; + // Pod variable that references the a source pod's root directory which is analogous to the + // Xcode $(SRCROOT) variable. + private const string PODS_VAR_TARGET_SRCROOT = "${PODS_TARGET_SRCROOT}"; + + // Version of the CocoaPods installation. private static string podsVersion = ""; + 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. + private static CommandLineDialog commandLineDialog = null; + // Mutex for access to commandLineDialog. + private static System.Object commandLineDialogLock = new System.Object(); + + // Regex that matches a "pod" specification line in a pod file. + // This matches the syntax... + // pod POD_NAME, OPTIONAL_VERSION, :PROPERTY0 => VALUE0 ... , :PROPERTYN => VALUE0 + private static Regex PODFILE_POD_REGEX = + new Regex( + // Extract the Cocoapod name and store in the podname group. + @"^\s*pod\s+'(?[^']+)'\s*" + + // Extract the version field and store in the podversion group. + @"(,\s*'(?[^']+)')?" + + // Match the end of the line or a list of property & value pairs. + // Property & value pairs are stored in propertyname / propertyvalue groups. + // Subsequent values are stored in the Captures property of each Group in the order + // they're matched. For example... + // 1 => 2, 3 => 4 + // associates Capture objects with values 1, 3 with group "propertyname" and + // Capture objects with values 2, 4 with group "propertyvalue". + @"(|" + + @"(,\s*:(?[^\s]+)\s*=>\s*(" + + // Unquoted property values. + @"(?[^\s,]+)\s*|" + + // Quoted string of the form 'foo'. + @"(?'[^']+')\s*|" + + // List of the form [1, 2, 3]. + @"(?\[[^\]]+\])\s*" + + @"))+" + + @")" + + @"$"); + + // 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( @@ -241,11 +647,24 @@ private static List FindFile( private static Assembly ResolveUnityEditoriOSXcodeExtension( object sender, ResolveEventArgs args) { + // Ignore null assembly references. + if (String.IsNullOrEmpty(args.Name)) return null; // The UnityEditor.iOS.Extensions.Xcode.dll has the wrong name baked // into the assembly so references end up resolving as // Unity.iOS.Extensions.Xcode. Catch this and redirect the load to // the UnityEditor.iOS.Extensions.Xcode. - string assemblyName = (new AssemblyName(args.Name)).Name; + string assemblyName; + try { + assemblyName = (new AssemblyName(args.Name)).Name; + } catch (Exception exception) { + // AssemblyName can throw if the DLL isn't found so try falling back to parsing the + // assembly name manually from the fully qualified name. + if (!(exception is FileLoadException || + exception is IOException)) { + throw exception; + } + assemblyName = args.Name.Split(new [] {','})[0]; + } if (!(assemblyName.Equals("Unity.iOS.Extensions.Xcode") || assemblyName.Equals("UnityEditor.iOS.Extensions.Xcode"))) { return null; @@ -304,40 +723,194 @@ 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 assemebly before the method is executed so + // will attempt to load the assembly before the method is executed so // we handle exceptions here. 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 (AutoPodToolInstallInEditorEnabled && CocoapodsIntegrationEnabled && + !ExecutionEnvironment.InBatchMode) { + 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 ((CocoapodsIntegrationMethod)settings.GetInt(PREFERENCE_COCOAPODS_INTEGRATION_METHOD, + CocoapodsIntegrationUpgradeDefault) == CocoapodsIntegrationMethod.None && + !ExecutionEnvironment.InBatchMode && !UpgradeToWorkspaceWarningDisabled) { + + 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", + 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/External Dependency Manager/iOS Resolver/Documentation")] + public static void OpenDocumentation() { + analytics.OpenUrl(VersionHandlerImpl.DocumentationUrl("#ios-resolver-usage"), "Usage"); + } + + // Display the iOS resolver settings menu. + [MenuItem("Assets/External Dependency Manager/iOS Resolver/Settings")] + public static void SettingsDialog() { + IOSResolverSettingsDialog window = (IOSResolverSettingsDialog) + EditorWindow.GetWindow(typeof(IOSResolverSettingsDialog), true, + "iOS Resolver Settings"); + window.Initialize(); + 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. @@ -349,307 +922,1070 @@ public static void RemapXcodeExtension() { } /// - /// Enable / disable iOS dependency injection. + /// Reset settings of this plugin to default values. /// - public static bool Enabled { - get { return EditorPrefs.GetBool(PREFERENCE_ENABLED, - defaultValue: true) && - iOSXcodeExtensionLoaded; } - set { EditorPrefs.SetBool(PREFERENCE_ENABLED, value); } + internal static void RestoreDefaultSettings() { + settings.DeleteKeys(PREFERENCE_KEYS); + analytics.RestoreDefaultSettings(); } /// - /// Log severity. + /// The method used to integrate Cocoapods with the build. /// - internal enum LogLevel { - Info, - Warning, - Error, + public enum CocoapodsIntegrationMethod { + None = 0, + Project, + Workspace }; /// - /// Log a message. + /// When first upgrading, decide on workspace integration based on previous settings. /// - internal static void Log(string message, bool verbose = false, - LogLevel level = LogLevel.Info) { - if (!verbose || verboseLogging) { - switch (level) { - case LogLevel.Info: - Debug.Log(message); - break; - case LogLevel.Warning: - Debug.LogWarning(message); - break; - case LogLevel.Error: - Debug.LogError(message); - break; - } + private static int CocoapodsIntegrationUpgradeDefault { + get { + return LegacyCocoapodsInstallEnabled ? + (int)CocoapodsIntegrationMethod.Workspace : + (int)CocoapodsIntegrationMethod.Project; } } /// - /// Determine whether a Pod is present in the list of dependencies. + /// IOSResolver Unity Preferences setting indicating which CocoaPods integration method to use. /// - public static bool PodPresent(string pod) { - return (new List(pods.Keys)).Contains(pod); + public static CocoapodsIntegrationMethod CocoapodsIntegrationMethodPref { + get { + return (CocoapodsIntegrationMethod)settings.GetInt( + PREFERENCE_COCOAPODS_INTEGRATION_METHOD, + defaultValue: CocoapodsIntegrationUpgradeDefault); + } + set { settings.SetInt(PREFERENCE_COCOAPODS_INTEGRATION_METHOD, (int)value); } } /// - /// Whether to inject iOS dependencies in the Unity generated Xcode - /// project. + /// Deprecated: Enable / disable CocoaPods installation. + /// Please use CocoapodsIntegrationEnabled instead. /// - private static bool InjectDependencies() { - return EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS && - Enabled && pods.Count > 0; + [System.Obsolete("CocoapodsInstallEnabled is deprecated, please use " + + "CocoapodsIntegrationEnabled instead.")] + public static bool CocoapodsInstallEnabled { + get { return LegacyCocoapodsInstallEnabled; } + set { LegacyCocoapodsInstallEnabled = value; } } /// - /// 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. + /// A formerly used setting for project integration. + /// It's kept as a private function to seed the default for the new setting: + /// CocoapodsIntegrationEnabled. /// - /// pod path, for example "Google-Mobile-Ads-SDK" to - /// be included - /// Version specification. See Pod.version. - /// 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. - public static void AddPod(string podName, string version = null, - bool bitcodeEnabled = true, - string minTargetSdk = null) { - Log("AddPod - name: " + podName + - " version: " + (version ?? "null") + - " bitcode: " + bitcodeEnabled.ToString() + - " sdk: " + (minTargetSdk ?? "null"), - verbose: true); - var pod = new Pod(podName, version, bitcodeEnabled, minTargetSdk); - pods[podName] = pod; - UpdateTargetSdk(pod); + private static bool LegacyCocoapodsInstallEnabled { + get { return settings.GetBool(PREFERENCE_COCOAPODS_INSTALL_ENABLED, + defaultValue: true); } + set { settings.SetBool(PREFERENCE_COCOAPODS_INSTALL_ENABLED, value); } } /// - /// Update the iOS target SDK if it's lower than the minimum SDK - /// version specified by the pod. + /// Enable / disable Podfile generation. /// - /// 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"); + public static bool PodfileGenerationEnabled { + get { return settings.GetBool(PREFERENCE_PODFILE_GENERATION_ENABLED, + defaultValue: true); } + set { + settings.SetBool(PREFERENCE_PODFILE_GENERATION_ENABLED, value); + SetEnablePollTargetSdks(value); } - return true; } /// - /// Update the target SDK if it's required. + /// Enable / disable polling of target iOS and tvOS SDK project setting values. /// - /// true if the SDK was updated, false otherwise. - public static bool UpdateTargetSdk() { - var minVersionAndPodNames = TargetSdkNeedsUpdate(); - 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; - } + 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; } - return false; } /// - /// Determine whether the target SDK needs to be updated based upon pod - /// dependencies. + /// Enable / disable execution of the pod tool via the shell. /// - /// Key value pair of minimum SDK version (key) and - /// a list of pod names that require it (value) if the currently - /// selected target SDK version does not satify 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; - } - return minVersionAndPodName; + public static bool PodToolExecutionViaShellEnabled { + get { return settings.GetBool(PREFERENCE_POD_TOOL_EXECUTION_VIA_SHELL_ENABLED, + defaultValue: true); } + set { settings.SetBool(PREFERENCE_POD_TOOL_EXECUTION_VIA_SHELL_ENABLED, value); } } - // Get the path of an xcode project relative to the specified directory. - private static string GetProjectPath(string relativeTo, - string projectName) { - return Path.Combine(relativeTo, - Path.Combine(projectName + ".xcodeproj", - "project.pbxproj")); + /// + /// 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); } } /// - /// Get the generated xcode project path relative to the specified - /// directory. + /// Enable automated pod tool installation in the editor. This is only performed when the + /// editor isn't launched in batch mode. /// - /// Path the project is relative to. - public static string GetProjectPath(string relativeTo) { - return GetProjectPath(relativeTo, PROJECT_NAME); + public static bool AutoPodToolInstallInEditorEnabled { + get { return settings.GetBool(PREFERENCE_AUTO_POD_TOOL_INSTALL_IN_EDITOR, + defaultValue: true); } + set { settings.SetBool(PREFERENCE_AUTO_POD_TOOL_INSTALL_IN_EDITOR, value); } } /// - /// Get or set the Unity iOS target SDK version string (e.g "7.1") - /// build setting. + /// Get / set the nag prompt disabler setting for turning on workspace integration. /// - static string TargetSdk { - get { - string name = null; - var iosSettingsType = typeof(UnityEditor.PlayerSettings.iOS); - // Read the version (Unity 5.5 and above). - 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 name.Trim().Replace("iOS_", "").Replace("_", "."); - } + public static bool UpgradeToWorkspaceWarningDisabled { + get { return settings.GetBool(PREFERENCE_WARN_UPGRADE_WORKSPACE, defaultValue: false); } + set { settings.SetBool(PREFERENCE_WARN_UPGRADE_WORKSPACE, value); } + } + /// + /// Enable / disable verbose logging. + /// + public static bool VerboseLoggingEnabled { + get { return settings.GetBool(PREFERENCE_VERBOSE_LOGGING_ENABLED, defaultValue: false); } set { - var iosSettingsType = typeof(UnityEditor.PlayerSettings.iOS); - // Write the version (Unity 5.5 and above). - var osVersionProperty = - iosSettingsType.GetProperty("targetOSVersionString"); - if (osVersionProperty != null) { - osVersionProperty.SetValue(null, value, null); - } else { - osVersionProperty = - iosSettingsType.GetProperty("targetOSVersion"); - osVersionProperty.SetValue( - null, - Enum.Parse(osVersionProperty.PropertyType, - "iOS_" + value.Replace(".", "_")), - null); - } + settings.SetBool(PREFERENCE_VERBOSE_LOGGING_ENABLED, value); + UpdateLoggerLevel(value); } } + private static void UpdateLoggerLevel(bool verboseLoggingEnabled) { + logger.Level = verboseLoggingEnabled ? LogLevel.Verbose : LogLevel.Info; + } + /// - /// Get or set the Unity iOS target SDK using a version number (e.g 71 - /// is equivalent to "7.1"). + /// Skip pod install when using workspace integration, let user manually run it. /// - static int TargetSdkVersion { - get { return TargetSdkStringToVersion(TargetSdk); } - set { TargetSdk = TargetSdkVersionToString(value); } + public static bool SkipPodInstallWhenUsingWorkspaceIntegration { + get { return settings.GetBool(PREFERENCE_SKIP_POD_INSTALL_WHEN_USING_WORKSPACE_INTEGRATION, + defaultValue: false); } + set { settings.SetBool(PREFERENCE_SKIP_POD_INSTALL_WHEN_USING_WORKSPACE_INTEGRATION, + value); } } /// - /// Convert a target SDK string into a value of the form - // (major * 10) + minor. + /// Whether to add "use_frameworks!" in the Podfile. True by default. + /// If true, iOS Resolver adds the following line to Podfile. + /// + /// use_frameworks! /// - /// Integer representation of the SDK. - internal static int TargetSdkStringToVersion(string targetSdk) { - if (TARGET_SDK_REGEX.IsMatch(targetSdk)) { - try { - return Convert.ToInt32(targetSdk.Replace(".", "")); - } catch (FormatException) { - // Conversion failed, drop through. - } + public static bool PodfileAddUseFrameworks { + get { return settings.GetBool(PREFERENCE_PODFILE_ADD_USE_FRAMEWORKS, + defaultValue: true); } + set { + settings.SetBool(PREFERENCE_PODFILE_ADD_USE_FRAMEWORKS, value); } - Log(String.Format( - "Invalid iOS target SDK version configured \"{0}\".\n" + - "\n" + - "Please change this to a valid SDK version (e.g {1}) in:\n" + + } + + /// + /// 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. + /// + public static bool UseProjectSettings { + get { return settings.UseProjectSettings; } + set { settings.UseProjectSettings = value; } + } + + /// + /// Determine whether it's possible to perform iOS dependency injection. + /// + public static bool Enabled { get { return iOSXcodeExtensionLoaded; } } + + private const float epsilon = 1e-7f; + + /// + /// Whether or not Unity can load a workspace file if it's present. + /// + private static bool UnityCanLoadWorkspace { + get { + // Unity started supporting workspace loading in the released version of Unity 5.6 + // but not in the beta. So check if this is exactly 5.6, but also beta. + if (Math.Abs( + VersionHandler.GetUnityVersionMajorMinor() - 5.6f) < epsilon) { + // Unity non-beta versions look like 5.6.0f1 while beta versions look like: + // 5.6.0b11, so looking for the b in the string (especially confined to 5.6), + // should be sufficient for determining that it's the beta. + if (UnityEngine.Application.unityVersion.Contains(".0b")) { + return false; + } + } + // 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.ToLower().Contains("-bvrbuildtarget")) { + return false; + } + return (VersionHandler.GetUnityVersionMajorMinor() >= 5.6f - epsilon); + } + } + + /// + /// Whether or not we should do Xcode workspace level integration of CocoaPods. + /// False if the Unity version doesn't support loading workspaces. + /// + private static bool CocoapodsWorkspaceIntegrationEnabled { + get { + return UnityCanLoadWorkspace && + CocoapodsIntegrationMethodPref == CocoapodsIntegrationMethod.Workspace; + } + } + + /// + /// Whether or not we should do Xcode project level integration of CocoaPods. + /// True if configured for project integration or workspace integration is enabled but using + /// an older version of Unity that doesn't support loading workspaces (as a fallback). + /// + private static bool CocoapodsProjectIntegrationEnabled { + get { + return CocoapodsIntegrationMethodPref == CocoapodsIntegrationMethod.Project || + (!UnityCanLoadWorkspace && + CocoapodsIntegrationMethodPref == CocoapodsIntegrationMethod.Workspace); + } + } + + /// + /// Whether or not we are integrating the pod dependencies into an Xcode build that Unity loads. + /// + public static bool CocoapodsIntegrationEnabled { + get { + return (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS || + EditorUserBuildSettings.activeBuildTarget == BuildTarget.tvOS) && + CocoapodsIntegrationMethodPref != CocoapodsIntegrationMethod.None; + } + } + + private delegate void LogMessageDelegate(string message, bool verbose = false, + LogLevel level = LogLevel.Info); + + 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. + /// + /// Message to log. + /// Whether the message should only be displayed if verbose logging is + /// enabled. + /// Severity of the message. + internal static void Log(string message, bool verbose = false, + LogLevel level = LogLevel.Info) { + logger.Log(message, level: verbose ? LogLevel.Verbose : level); + } + + /// + /// Display a message in a dialog and log to the console. + /// + internal static void LogToDialog(string message, bool verbose = false, + LogLevel level = LogLevel.Info) { + if (!verbose) { + DialogWindow.Display("iOS Resolver", message, DialogWindow.Option.Selected0, "OK"); + } + Log(message, verbose: verbose, level: level); + } + + /// + /// Determine whether a Pod is present in the list of dependencies. + /// + public static bool PodPresent(string pod) { + return (new List(pods.Keys)).Contains(pod); + } + + /// + /// Whether to inject iOS dependencies in the Unity generated Xcode + /// project. + /// + private static bool InjectDependencies() { + return (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS || + EditorUserBuildSettings.activeBuildTarget == BuildTarget.tvOS) && + Enabled && pods.Count > 0; + } + + /// + /// Convert dependency version specifications to the version expression used by pods. + /// + /// + /// Version specification string. + /// + /// If it ends with "+" the specified version up to the next major + /// version is selected. + /// If "LATEST", null or empty this pulls the latest revision. + /// A version number "1.2.3" selects a specific version number. + /// + /// The version expression formatted for pod dependencies. + /// For example, "1.2.3+" would become "~> 1.2.3". + private static string PodVersionExpressionFromVersionDep(string dependencyVersion) { + if (String.IsNullOrEmpty(dependencyVersion) || dependencyVersion.Equals("LATEST")) { + return null; + } + if (dependencyVersion.EndsWith("+")) { + return String.Format("~> {0}", + dependencyVersion.Substring(0, dependencyVersion.Length - 1)); + } + return dependencyVersion; + } + + /// + /// 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. + /// 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, + IEnumerable sources = null) { + AddPodInternal(podName, + preformattedVersion: PodVersionExpressionFromVersionDep(version), + bitcodeEnabled: bitcodeEnabled, minTargetSdk: minTargetSdk, + 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); + } + + /// + /// Same as AddPod except the version string is used in the pod declaration directly. + /// See AddPod. + /// + /// pod path, for example "Google-Mobile-Ads-SDK" to + /// be included + /// Podfile version specification similar to what is + /// returned by PodVersionExpressionFromVersionDep(). + /// 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. + /// Overwrite an existing pod. + /// Tag of the object that added this pod. + /// Whether this was added via an XML dependency. + /// Dictionary of additional properties for the pod + /// reference. + 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, + addToAllTargets, sources, propertiesByName); + pod.createdBy = createdBy ?? pod.createdBy; + pod.fromXmlFile = fromXmlFile; + + Log(String.Format( + "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), + verbose: true); + + Pod existingPod = null; + if (!overwriteExistingPod && pods.TryGetValue(podName, out existingPod)) { + // Only warn if the existing pod differs to the newly added pod. + if (!pod.Equals(existingPod)) { + Log(String.Format("Pod {0} already present, ignoring.\n" + + "Original declaration {1}\n" + + "Ignored declaration {2}\n", podName, + pods[podName].createdBy, createdBy ?? "(unknown)"), + level: LogLevel.Warning); + } + return; + } + pods[podName] = pod; + ScheduleCheckTargetIosSdkVersion(); + ScheduleCheckTargetTvosSdkVersion(); + } + + /// + /// Determine whether the target iOS SDK has changed. + /// + private static void PollTargetIosSdk() { + iosTargetSdkPoller.Poll(() => TargetIosSdkVersionString, + (previousValue, currentValue) => { ScheduleCheckTargetIosSdkVersion(); }); + } + + /// + /// Determine whether the target tvOS SDK has changed. + /// + 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"); + } + } + }); + } + } + + /// + /// Update the iOS target SDK if it's required. + /// + /// 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); + 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"); + } + } + }); + } + } + + /// + /// Determine whether the target SDK needs to be updated based upon pod + /// dependencies. + /// + /// 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(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; + } + } + // 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. + private static string GetProjectPath(string relativeTo, + string projectName) { + return Path.Combine(relativeTo, + Path.Combine(projectName + ".xcodeproj", + "project.pbxproj")); + } + + /// + /// Get the generated xcode project path relative to the specified + /// directory. + /// + /// Path the project is relative to. + public static string GetProjectPath(string relativeTo) { + return GetProjectPath(relativeTo, PROJECT_NAME); + } + + /// + /// Get or set the Unity iOS target SDK version string (e.g "7.1") + /// build setting dependeding on the current active build target. + /// + static string TargetIosSdkVersionString { + get { + string name = null; + var iosSettingsType = typeof(UnityEditor.PlayerSettings.iOS); + var osVersionProperty = + iosSettingsType.GetProperty("targetOSVersionString"); + if (osVersionProperty != null) { + name = (string)osVersionProperty.GetValue(null, null); + } + if (String.IsNullOrEmpty(name)) { + return TargetSdkVersionToString(DEFAULT_IOS_TARGET_SDK); + } + return name.Trim().Replace("iOS_", "").Replace("_", "."); + } + + set { + var iosSettingsType = typeof(UnityEditor.PlayerSettings.iOS); + // Write the version (Unity 5.5 and above). + var osVersionProperty = + iosSettingsType.GetProperty("targetOSVersionString"); + if (osVersionProperty != null) { + osVersionProperty.SetValue(null, value, null); + } else { + osVersionProperty = + iosSettingsType.GetProperty("targetOSVersion"); + osVersionProperty.SetValue( + null, + Enum.Parse(osVersionProperty.PropertyType, + "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 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. + /// + /// Integer representation of the SDK. + internal static int TargetSdkStringToVersion(string targetSdk) { + if (TARGET_SDK_REGEX.IsMatch(targetSdk)) { + try { + return Convert.ToInt32(targetSdk.Replace(".", "")); + } catch (FormatException) { + // Conversion failed, drop through. + } + } + Log(String.Format( + "Invalid iOS target SDK version configured \"{0}\".\n" + + "\n" + + "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; + + } + + /// + /// Convert an integer target SDK value into a string. + /// + /// String version number. + internal static string TargetSdkVersionToString(int version) { + int major = version / 10; + int minor = version % 10; + return major.ToString() + "." + minor.ToString(); + } + + /// + /// Determine whether any pods need bitcode disabled. + /// + /// List of pod names with bitcode disabled. + private static List FindPodsWithBitcodeDisabled() { + var disabled = new List(); + foreach (var pod in pods.Values) { + if (!pod.bitcodeEnabled) { + disabled.Add(pod.name); + } + } + return disabled; + } + + /// + /// Menu item that installs CocoaPods if it's not already installed. + /// + [MenuItem("Assets/External Dependency Manager/iOS Resolver/Install Cocoapods")] + public static void InstallCocoapodsMenu() { + InstallCocoapodsInteractive(); + } + + /// + /// Auto install CocoaPods tools if they're not already installed. + /// + public static void AutoInstallCocoapods() { + InstallCocoapodsInteractive(displayAlreadyInstalled: false); + } + + /// + /// Interactively installs CocoaPods if it's not already installed. + /// + public static void InstallCocoapodsInteractive(bool displayAlreadyInstalled = true) { + bool installCocoapods = true; + lock (commandLineDialogLock) { + if (commandLineDialog != null) { + // If the installation is still in progress, display the dialog. + commandLineDialog.Show(); + installCocoapods = false; + } + } + if (installCocoapods) { + InstallCocoapods(true, ".", displayAlreadyInstalled: displayAlreadyInstalled); + } + } + + /// + /// Determine whether a gem (Ruby package) is installed. + /// + /// Name of the package to check. + /// Delegate use to log a failure message if the package manager + /// returns an error code. + /// true if the package is installed, false otherwise. + private static bool QueryGemInstalled(string gemPackageName, + LogMessageDelegate logMessage = null) { + logMessage = logMessage ?? Log; + logMessage(String.Format("Determine whether Ruby Gem {0} is installed", gemPackageName), + verbose: true); + var query = String.Format("list {0} --no-versions", gemPackageName); + var result = RunCommand(GEM_EXECUTABLE, query); + if (result.exitCode == 0) { + foreach (var line in result.stdout.Split(new string[] { Environment.NewLine }, + StringSplitOptions.None)) { + if (line == gemPackageName) { + logMessage(String.Format("{0} is installed", gemPackageName), verbose: true); + return true; + } + } + } else { + logMessage( + String.Format("Unable to determine whether the {0} gem is " + + "installed, will attempt to install anyway.\n\n" + + "'{1} {2}' failed with error code ({3}):\n" + + "{4}\n" + + "{5}\n", + gemPackageName, GEM_EXECUTABLE, query, result.exitCode, + result.stdout, result.stderr), + level: LogLevel.Warning); + } + return false; + } + + /// + /// Install CocoaPods if it's not already installed. + /// + /// Whether this method should display information in pop-up + /// dialogs. + /// Where to run the pod tool's setup command. + /// Whether to display whether the tools are already + /// installed. + public static void InstallCocoapods(bool interactive, string workingDirectory, + bool displayAlreadyInstalled = true) { + cocoapodsToolsInstallPresent = false; + // Cocoapod tools are currently only available on OSX, don't attempt to install them + // otherwise. + if (UnityEngine.RuntimePlatform.OSXEditor != UnityEngine.Application.platform) { + return; + } + + LogMessageDelegate logMessage = null; + if (interactive) { + logMessage = LogToDialog; + } else { + logMessage = Log; + } + + var podToolPath = FindPodTool(); + if (!String.IsNullOrEmpty(podToolPath)) { + var installationFoundMessage = "CocoaPods installation detected " + podToolPath; + if (displayAlreadyInstalled) { + logMessage(installationFoundMessage, level: LogLevel.Verbose); + } + cocoapodsToolsInstallPresent = true; + return; + } + + var complete = new AutoResetEvent(false); + var commonInstallErrorMessage = + "It will not be possible to install Cocoapods in the generated Xcode " + + "project which will result in link errors when building your " + + "application.\n\n" + + "For more information see:\n" + + " https://guides.cocoapods.org/using/getting-started.html\n\n"; + + 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 + // user install. When RVM is configured "--user-install" ends up installing gems + // in the wrong directory such that they're not visible to either the package manager + // or Ruby. + var gemEnvironment = ReadGemsEnvironment(); + string installArgs = "--user-install"; + if (gemEnvironment != null) { + List installationDir; + if (gemEnvironment.TryGetValue("INSTALLATION DIRECTORY", out installationDir)) { + foreach (var dir in installationDir) { + if (dir.IndexOf("/.rvm/") >= 0) { + installArgs = ""; + break; + } + } + } + } + if (VerboseLoggingEnabled || ExecutionEnvironment.InBatchMode) { + 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 + // Ruby 2.2.2 and above. + // https://github.com/CocoaPods/CocoaPods/issues/4711 + commandList.Add( + new CommandItem { + Command = GEM_EXECUTABLE, + Arguments = "install activesupport -v 4.2.6 " + installArgs + }); + } + commandList.Add(new CommandItem { + Command = GEM_EXECUTABLE, + Arguments = "install cocoapods " + installArgs + }); + commandList.Add(new CommandItem { Command = POD_EXECUTABLE, Arguments = "setup" }); + + RunCommandsAsync( + commandList.ToArray(), + (int commandIndex, CommandItem[] commands, CommandLine.Result result, + CommandLineDialog dialog) => { + 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" + + "'{1} {2}' failed with code ({3}):\n" + + "{4}\n\n" + + "{5}\n", + commonInstallErrorMessage, lastCommand.Command, + lastCommand.Arguments, result.exitCode, result.stdout, + result.stderr), level: LogLevel.Error); + complete.Set(); + return -1; + } + // Pod setup process (should be the last command in the list). + if (commandIndex == commands.Length - 1) { + podToolPath = FindPodTool(); + if (String.IsNullOrEmpty(podToolPath)) { + logMessage(String.Format( + "'{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; + } + return commandIndex; + }, displayDialog: interactive, summaryText: "Installing CocoaPods..."); + // If this wasn't started interactively, block until execution is complete. + if (!interactive) complete.WaitOne(); } /// - /// Convert an integer target SDK value into a string. + /// Called by Unity when all assets have been updated. This refreshes the Pods loaded from + /// XML files. /// - /// String version number. - internal static string TargetSdkVersionToString(int version) { - int major = version / 10; - int minor = version % 10; - return major.ToString() + "." + minor.ToString(); + /// 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(); } /// - /// Determine whether any pods need bitcode disabled. + /// Refresh XML dependencies if the plugin is enabled. /// - /// List of pod names with bitcode disabled. - private static List FindPodsWithBitcodeDisabled() { - var disabled = new List(); - foreach (var pod in pods.Values) { - if (!pod.bitcodeEnabled) { - disabled.Add(pod.name); - } - } - return disabled; + /// Unused + /// Unused + [PostProcessBuildAttribute(BUILD_ORDER_REFRESH_DEPENDENCIES)] + public static void OnPostProcessRefreshXmlDependencies(BuildTarget buildTarget, + string pathToBuiltProject) { + if (!CocoapodsIntegrationEnabled) return; + RefreshXmlDependencies(); + } + + /// + /// If Cocoapod installation is enabled, prompt the user to install CocoaPods if it's not + /// present on the machine. + /// + [PostProcessBuildAttribute(BUILD_ORDER_CHECK_COCOAPODS_INSTALL)] + public static void OnPostProcessEnsurePodsInstallation(BuildTarget buildTarget, + string pathToBuiltProject) { + if (!CocoapodsIntegrationEnabled) return; + InstallCocoapods(false, pathToBuiltProject); } /// @@ -658,10 +1994,96 @@ private static List FindPodsWithBitcodeDisabled() { [PostProcessBuildAttribute(BUILD_ORDER_PATCH_PROJECT)] public static void OnPostProcessPatchProject(BuildTarget buildTarget, string pathToBuiltProject) { - if (!InjectDependencies()) return; + if (!InjectDependencies() || !PodfileGenerationEnabled || + !CocoapodsProjectIntegrationEnabled || !cocoapodsToolsInstallPresent) { + return; + } 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 @@ -671,30 +2093,58 @@ internal static void PatchProject( var podsWithoutBitcode = FindPodsWithBitcodeDisabled(); bool bitcodeDisabled = podsWithoutBitcode.Count > 0; if (bitcodeDisabled) { - Log("Bitcode is disabled due to the following Cocoapods (" + + Log("Bitcode is disabled due to the following CocoaPods (" + String.Join(", ", podsWithoutBitcode.ToArray()) + ")", level: LogLevel.Warning); } - // Configure project settings for Cocoapods. + // Configure project settings for CocoaPods. 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", "$(inherited)"); - project.AddBuildProperty(target, "OTHER_CFLAGS", "$(inherited)"); - project.AddBuildProperty(target, "HEADER_SEARCH_PATHS", - "$(inherited)"); - project.AddBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", - "$(inherited)"); - project.AddBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", - "$(PROJECT_DIR)/Frameworks"); - 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()); } @@ -704,28 +2154,298 @@ internal static void PatchProject( [PostProcessBuildAttribute(BUILD_ORDER_GEN_PODFILE)] public static void OnPostProcessGenPodfile(BuildTarget buildTarget, string pathToBuiltProject) { - if (!InjectDependencies()) return; + if (!InjectDependencies() || !PodfileGenerationEnabled) return; GenPodfile(buildTarget, pathToBuiltProject); } + /// + /// Get the path to the generated Podfile. + /// + private static string GetPodfilePath(string pathToBuiltProject) { + return Path.Combine(pathToBuiltProject, "Podfile"); + } + + /// + /// Checks to see if a podfile, not written by the IOSResolver is present. + /// + /// The path we suspect is written by Unity. This is + /// either the original file or a backup of the path. + /// The path to the Podfile, presumed to be generated by Unity. + private static string FindExistingUnityPodfile(string suspectedUnityPodfilePath) { + if (!File.Exists(suspectedUnityPodfilePath)) return null; + + System.IO.StreamReader podfile = new System.IO.StreamReader(suspectedUnityPodfilePath); + string firstline = podfile.ReadLine(); + podfile.Close(); + // If the podfile written is one that we created, then we need to look for the backup of the + // original Unity podfile. This is necessary for cases when the user does an "append build" + // in Unity. Since we back up the original podfile, we'll re-parse it when regenerating + // the dependencies this time around. + if (firstline == null || firstline.StartsWith(PODFILE_GENERATED_COMMENT)) { + return FindExistingUnityPodfile(suspectedUnityPodfilePath + + UNITY_PODFILE_BACKUP_POSTFIX); + } + + return suspectedUnityPodfilePath; + } + + private static void ParseUnityDeps(string unityPodfilePath) { + Log("Parse Unity deps from: " + unityPodfilePath, verbose: true); + + System.IO.StreamReader unityPodfile = new System.IO.StreamReader(unityPodfilePath); + string line; + + // We are only interested in capturing the dependencies "Pod depName, depVersion", inside + // of the specific target. However there can be nested targets such as for testing, so we're + // counting the depth to determine when to capture the pods. Also we only ever enter the + // first depth if we're in the exact right target. + int capturingPodsDepth = 0; + 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; + } + // TODO: Properly support multiple targets. + if (line.StartsWith(String.Format("target '{0}' do", + XcodeTargetWithUnityLibraries))) { + capturingPodsDepth++; + continue; + } + + if (capturingPodsDepth == 0) continue; + + // handle other scopes roughly + if (line.EndsWith(" do")) { + capturingPodsDepth++; // Ignore nested targets like tests + } else if (line == "end") { + capturingPodsDepth--; + } + + if (capturingPodsDepth != 1) continue; + + // Parse "pod" lines from the default target in the file. + var podLineMatch = PODFILE_POD_REGEX.Match(line); + var podGroups = podLineMatch.Groups; + if (podGroups.Count > 1) { + var podName = podGroups["podname"].ToString(); + var podVersion = podGroups["podversion"].ToString(); + var propertyNameCaptures = podGroups["propertyname"].Captures; + var propertyValueCaptures = podGroups["propertyvalue"].Captures; + var numberOfProperties = propertyNameCaptures.Count; + var propertiesByName = new Dictionary(); + for (int i = 0; i < numberOfProperties; ++i) { + propertiesByName[propertyNameCaptures[i].Value] = + propertyValueCaptures[i].Value; + } + AddPodInternal( + podName, + preformattedVersion: String.IsNullOrEmpty(podVersion) ? null : podVersion, + sources: sources, createdBy: unityPodfilePath, overwriteExistingPod: false, + propertiesByName: propertiesByName); + } + } + unityPodfile.Close(); + } + + /// + /// Generate the sources section from the set of "pods" in this class. + /// + /// Each source is interleaved across each pod - removing duplicates - as CocoaPods searches + /// each source in order for each pod. + /// + /// See Pod.sources for more information. + /// + /// String which contains the sources section of a Podfile. For example, if the + /// Pod instances referenced by this class contain sources... + /// + /// ["http://myrepo.com/Specs.git", "http://anotherrepo.com/Specs.git"] + /// + /// this returns the string... + /// + /// source 'http://myrepo.com/Specs.git' + /// source 'http://anotherrepo.com/Specs.git' + private static string GeneratePodfileSourcesSection() { + var interleavedSourcesLines = new List(); + var processedSources = new HashSet(); + int sourceIndex = 0; + bool sourcesAvailable; + foreach (var kv in Pod.Sources) { + interleavedSourcesLines.Add(String.Format("source '{0}'", kv.Key)); + } + do { + sourcesAvailable = false; + foreach (var pod in pods.Values) { + if (sourceIndex < pod.sources.Count) { + sourcesAvailable = true; + var source = pod.sources[sourceIndex]; + if (processedSources.Add(source)) { + interleavedSourcesLines.Add(String.Format("source '{0}'", source)); + } + } + } + sourceIndex ++; + } while (sourcesAvailable); + return String.Join("\n", interleavedSourcesLines.ToArray()) + "\n"; + } + // Implementation of OnPostProcessGenPodfile(). // NOTE: This is separate from the post-processing method to prevent the // Mono runtime from loading the Xcode API before calling the post // processing step. public static void GenPodfile(BuildTarget buildTarget, string pathToBuiltProject) { - using (StreamWriter file = - new StreamWriter(Path.Combine(pathToBuiltProject, "Podfile"))) { - file.Write("source 'https://github.com/CocoaPods/Specs.git'\n" + - "install! 'cocoapods', :integrate_targets => false\n" + - string.Format("platform :ios, '{0}'\n\n", TargetSdk) + - "target '" + TARGET_NAME + "' do\n" - ); - foreach(var pod in pods.Values) { - file.WriteLine(pod.PodFilePodLine); + analytics.Report("generatepodfile", "Generate Podfile"); + string podfilePath = GetPodfilePath(pathToBuiltProject); + + string unityPodfile = FindExistingUnityPodfile(podfilePath); + Log(String.Format("Detected Unity Podfile: {0}", unityPodfile), verbose: true); + if (unityPodfile != null) { + ParseUnityDeps(unityPodfile); + if (podfilePath == unityPodfile) { + string unityBackupPath = podfilePath + UNITY_PODFILE_BACKUP_POSTFIX; + if (File.Exists(unityBackupPath)) { + File.Delete(unityBackupPath); + } + File.Move(podfilePath, unityBackupPath); + } + } + + Log(String.Format("Generating Podfile {0} with {1} integration.", podfilePath, + (CocoapodsWorkspaceIntegrationEnabled ? "Xcode workspace" : + (CocoapodsProjectIntegrationEnabled ? "Xcode project" : "no target"))), + verbose: true); + + using (StreamWriter file = new StreamWriter(podfilePath)) { + 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!"); + } + } + + 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"); + } + + /// + /// Read the Gems environment. + /// + /// Dictionary of environment properties or null if there was a problem reading + /// the environment. + private static Dictionary> ReadGemsEnvironment() { + var result = RunCommand(GEM_EXECUTABLE, "environment"); + if (result.exitCode != 0) { + return null; + } + // gem environment outputs YAML for all config variables. Perform some very rough YAML + // parsing to get the environment into a usable form. + var gemEnvironment = new Dictionary>(); + const string listItemPrefix = "- "; + int previousIndentSize = 0; + List currentList = null; + char[] listToken = new char[] { ':' }; + foreach (var line in result.stdout.Split(new char[] { '\r', '\n' })) { + var trimmedLine = line.Trim(); + var indentSize = line.Length - trimmedLine.Length; + if (indentSize < previousIndentSize) currentList = null; + + if (trimmedLine.StartsWith(listItemPrefix)) { + trimmedLine = trimmedLine.Substring(listItemPrefix.Length).Trim(); + if (currentList == null) { + var tokens = trimmedLine.Split(listToken); + currentList = new List(); + gemEnvironment[tokens[0].Trim()] = currentList; + var value = tokens.Length == 2 ? tokens[1].Trim() : null; + if (!String.IsNullOrEmpty(value)) { + currentList.Add(value); + currentList = null; + } + } else if (indentSize >= previousIndentSize) { + currentList.Add(trimmedLine); + } + } else { + currentList = null; } - file.WriteLine("end"); + previousIndentSize = indentSize; } + return gemEnvironment; } /// @@ -735,32 +2455,30 @@ public static void GenPodfile(BuildTarget buildTarget, private static string FindPodTool() { foreach (string path in POD_SEARCH_PATHS) { string podPath = Path.Combine(path, POD_EXECUTABLE); - Log("Searching for cocoapods tool in " + podPath, + Log("Searching for CocoaPods tool in " + podPath, verbose: true); if (File.Exists(podPath)) { - Log("Found cocoapods tool in " + podPath, verbose: true); + Log("Found CocoaPods tool in " + podPath, verbose: true); return podPath; } } - Log("Querying gems for cocoapods install path", verbose: true); - var result = CommandLine.Run("gem", "environment"); - if (result.exitCode == 0) { - // gem environment outputs YAML for all config variables, - // the following code only parses the executable dir from the - // output. - const string executableDir = "- EXECUTABLE DIRECTORY:"; - char[] variableSeparator = new char[] { ':' }; - foreach (var line in result.stdout.Split( - new char[] { '\r', '\n' })) { - if (line.Trim().StartsWith(executableDir)) { - string path = line.Split(variableSeparator)[1].Trim(); - string podPath = Path.Combine(path, POD_EXECUTABLE); - Log("Checking gems install path for cocoapods tool " + - podPath, verbose: true); - if (File.Exists(podPath)) { - Log("Found cocoapods tool in " + podPath, + Log("Querying gems for CocoaPods install path", verbose: true); + var environment = ReadGemsEnvironment(); + if (environment != null) { + const string executableDir = "EXECUTABLE DIRECTORY"; + foreach (string environmentVariable in new [] { executableDir, "GEM PATHS" }) { + List paths; + if (environment.TryGetValue(environmentVariable, out paths)) { + foreach (var path in paths) { + var binPath = environmentVariable == executableDir ? path : + Path.Combine(path, "bin"); + var podPath = Path.Combine(binPath, POD_EXECUTABLE); + Log("Checking gems install path for CocoaPods tool " + podPath, verbose: true); - return podPath; + if (File.Exists(podPath)) { + Log("Found CocoaPods tool in " + podPath, verbose: true); + return podPath; + } } } } @@ -768,6 +2486,226 @@ private static string FindPodTool() { return null; } + + /// + /// Command line command to execute. + /// + private class CommandItem { + /// + /// Command to execute. + /// + public string Command { get; set; } + /// + /// Arguments for the command. + /// + public string Arguments { get; set; } + /// + /// Directory to execute the command. + /// + public string WorkingDirectory { get; set; } + /// + /// Get a string representation of the command line. + /// + public override string ToString() { + return String.Format("{0} {1}", Command, Arguments ?? ""); + } + }; + + /// + /// Called when one of the commands complete in RunCommandsAsync(). + /// + /// Index of the completed command in commands. + /// Array of commands being executed. + /// Result of the last command. + /// Dialog box, if the command was executed in a dialog. + /// Reference to the next command in the list to execute, + /// -1 or commands.Length to stop execution. + private delegate int CommandItemCompletionHandler( + int commandIndex, CommandItem[] commands, + CommandLine.Result result, CommandLineDialog dialog); + + /// + /// Container for a delegate which enables a lambda to reference itself. + /// + private class DelegateContainer { + /// + /// Delegate method associated with the container. This enables the + /// following pattern: + /// + /// var container = new DelegateContainer<CommandLine.CompletionHandler>(); + /// container.Handler = (CommandLine.Result result) => { RunNext(container.Handler); }; + /// + public T Handler { get; set; } + } + + /// + /// Write the result of a command to the log. + /// + /// Command that was executed. + /// Result of the command. + private static void LogCommandLineResult(string command, CommandLine.Result result) { + Log(String.Format("'{0}' completed with code {1}\n\n" + + "{2}\n" + + "{3}\n", command, result.exitCode, result.stdout, result.stderr), + verbose: true); + } + + /// + /// Run a series of commands asynchronously optionally displaying a dialog. + /// + /// Commands to execute. + /// Called when the command is complete. + /// Whether to show a dialog while executing. + /// Text to display at the top of the dialog. + private static void RunCommandsAsync(CommandItem[] commands, + CommandItemCompletionHandler completionDelegate, + bool displayDialog = false, string summaryText = null) { + var envVars = new Dictionary() { + // CocoaPods requires a UTF-8 terminal, otherwise it displays a warning. + {"LANG", (System.Environment.GetEnvironmentVariable("LANG") ?? + "en_US.UTF-8").Split('.')[0] + ".UTF-8"}, + {"PATH", ("/usr/local/bin:" + + (System.Environment.GetEnvironmentVariable("PATH") ?? ""))}, + }; + + if (displayDialog) { + var dialog = CommandLineDialog.CreateCommandLineDialog("iOS Resolver"); + dialog.modal = false; + dialog.autoScrollToBottom = true; + dialog.bodyText = commands[0].ToString() + "\n"; + dialog.summaryText = summaryText ?? dialog.bodyText; + dialog.logger = logger; + + int index = 0; + var handlerContainer = new DelegateContainer(); + handlerContainer.Handler = (CommandLine.Result asyncResult) => { + var command = commands[index]; + LogCommandLineResult(command.ToString(), asyncResult); + + index = completionDelegate(index, commands, asyncResult, dialog); + bool endOfCommandList = index < 0 || index >= commands.Length; + if (endOfCommandList) { + // If this is the last command and it has completed successfully, close the + // dialog. + if (asyncResult.exitCode == 0) { + dialog.Close(); + } + lock (commandLineDialogLock) { + commandLineDialog = null; + } + } else { + command = commands[index]; + var commandLogLine = command.ToString(); + dialog.bodyText += "\n" + commandLogLine + "\n\n"; + Log(commandLogLine, verbose: true); + dialog.RunAsync(command.Command, command.Arguments, handlerContainer.Handler, + workingDirectory: command.WorkingDirectory, + envVars: envVars); + } + }; + + Log(commands[0].ToString(), verbose: true); + dialog.RunAsync( + commands[index].Command, commands[index].Arguments, + handlerContainer.Handler, workingDirectory: commands[index].WorkingDirectory, + envVars: envVars); + dialog.Show(); + lock (commandLineDialogLock) { + commandLineDialog = dialog; + } + } else { + if (!String.IsNullOrEmpty(summaryText)) Log(summaryText); + + int index = 0; + while (index >= 0 && index < commands.Length) { + var command = commands[index]; + Log(command.ToString(), verbose: true); + var result = CommandLine.RunViaShell( + command.Command, command.Arguments, workingDirectory: command.WorkingDirectory, + envVars: envVars, useShellExecution: PodToolExecutionViaShellEnabled, + setLangInShellMode: PodToolShellExecutionSetLang); + LogCommandLineResult(command.ToString(), result); + index = completionDelegate(index, commands, result, null); + } + } + } + + + /// + /// Run a command, optionally displaying a dialog. + /// + /// Command to execute. + /// Arguments passed to the command. + /// Called when the command is complete. + /// Where to run the command. + /// Whether to show a dialog while executing. + /// Text to display at the top of the dialog. + private static void RunCommandAsync(string command, string commandArgs, + CommandLine.CompletionHandler completionDelegate, + string workingDirectory = null, + bool displayDialog = false, string summaryText = null) { + RunCommandsAsync( + new [] { new CommandItem { Command = command, Arguments = commandArgs, + WorkingDirectory = workingDirectory } }, + (int commandIndex, CommandItem[] commands, CommandLine.Result result, + CommandLineDialog dialog) => { + completionDelegate(result); + return -1; + }, displayDialog: displayDialog, summaryText: summaryText); + } + + /// + /// Run a command, optionally displaying a dialog. + /// + /// Command to execute. + /// Arguments passed to the command. + /// Where to run the command. + /// Whether to show a dialog while executing. + /// The CommandLine.Result from running the command. + private static CommandLine.Result RunCommand(string command, string commandArgs, + string workingDirectory = null, + bool displayDialog = false) { + CommandLine.Result result = null; + var complete = new AutoResetEvent(false); + RunCommandAsync(command, commandArgs, + (CommandLine.Result asyncResult) => { + result = asyncResult; + complete.Set(); + }, workingDirectory: workingDirectory, displayDialog: displayDialog); + complete.WaitOne(); + return result; + + } + + /// + /// Finds and executes the pod command on the command line, using the + /// correct environment. + /// + /// Arguments passed to the pod command. + /// The path to the unity project, given + /// from the unity [PostProcessBuildAttribute()] function. + /// Called when the command is complete. + /// Whether to execute in a dialog. + /// Text to display at the top of the dialog. + private static void RunPodCommandAsync( + string podArgs, string pathToBuiltProject, + CommandLine.CompletionHandler completionDelegate, + bool displayDialog = false, string summaryText = null) { + string podCommand = FindPodTool(); + if (String.IsNullOrEmpty(podCommand)) { + var result = new CommandLine.Result(); + result.exitCode = 1; + result.stderr = String.Format( + "'{0}' command not found; unable to generate a usable Xcode project.\n{1}", + POD_EXECUTABLE, COCOAPOD_INSTALL_INSTRUCTIONS); + Log(result.stderr, level: LogLevel.Error); + completionDelegate(result); + } + RunCommandAsync(podCommand, podArgs, completionDelegate, + workingDirectory: pathToBuiltProject, displayDialog: displayDialog, + summaryText: summaryText); + } + /// /// Finds and executes the pod command on the command line, using the /// correct environment. @@ -775,26 +2713,19 @@ private static string FindPodTool() { /// Arguments passed to the pod command. /// The path to the unity project, given /// from the unity [PostProcessBuildAttribute()] function. + /// Whether to execute in a dialog. /// The CommandLine.Result from running the command. - private static CommandLine.Result RunPodCommand(string podArgs, - string pathToBuiltProject) { - string pod_command = FindPodTool(); - if (String.IsNullOrEmpty(pod_command)) { - CommandLine.Result r = new CommandLine.Result(); - r.exitCode = 1; - r.stderr = "'pod' command not found; unable to generate a usable" + - " Xcode project. " + COCOAPOD_INSTALL_INSTRUCTIONS; - Log(r.stderr, level: LogLevel.Error); - return r; - } - - return CommandLine.Run( - pod_command, podArgs, pathToBuiltProject, - // cocoapods seems to require this, or it spits out a warning. - envVars: new Dictionary() { - {"LANG", (System.Environment.GetEnvironmentVariable("LANG") ?? - "en_US.UTF-8").Split('.')[0] + ".UTF-8"} - }); + private static CommandLine.Result RunPodCommand( + string podArgs, string pathToBuiltProject, bool displayDialog = false) { + CommandLine.Result result = null; + var complete = new AutoResetEvent(false); + RunPodCommandAsync(podArgs, pathToBuiltProject, + (CommandLine.Result asyncResult) => { + result = asyncResult; + complete.Set(); + }, displayDialog: displayDialog); + complete.WaitOne(); + return result; } /// @@ -803,16 +2734,51 @@ private static CommandLine.Result RunPodCommand(string podArgs, [PostProcessBuildAttribute(BUILD_ORDER_INSTALL_PODS)] public static void OnPostProcessInstallPods(BuildTarget buildTarget, string pathToBuiltProject) { - if (!InjectDependencies()) return; - if (UpdateTargetSdk()) return; + if (!InjectDependencies() || !PodfileGenerationEnabled) 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" + + "If CocoaPods are not installed in your project it will not link.\n\n" + + "The command '{0} install' must be executed from the {1} directory to generate " + + "a Xcode workspace that includes the CocoaPods referenced by {2}.\n" + + "For more information see:\n" + + " https://guides.cocoapods.org/using/using-cocoapods.html\n\n", + POD_EXECUTABLE, pathToBuiltProject, GetPodfilePath(pathToBuiltProject)), + level: LogLevel.Warning); + return; + } + // Skip running "pod install" if requested. This is helpful if the user want to run pod tool + // manually, in case pod tool customizations are necessary (custom flag or repo setup). + if (UnityCanLoadWorkspace && + CocoapodsIntegrationMethodPref == CocoapodsIntegrationMethod.Workspace && + SkipPodInstallWhenUsingWorkspaceIntegration) { + analytics.Report("installpods/disabled", "Pod Install Disabled"); + Log("Skipping pod install.", level: LogLevel.Warning); + return; + } // Require at least version 1.0.0 CommandLine.Result result; result = RunPodCommand("--version", pathToBuiltProject); if (result.exitCode == 0) podsVersion = result.stdout.Trim(); - - if (result.exitCode != 0 || podsVersion[0] == '0') { - Log("Error running cocoapods. Please ensure you have at least " + + 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: " + result.exitCode.ToString() + "\n" + @@ -821,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. @@ -839,14 +2810,16 @@ 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 " + + "CocoaPods installation failure. This will will likely " + "result in an non-functional Xcode project.\n\n" + "After the failure, \"pod repo update\" " + "was executed and " + (repoUpdateSucceeded ? "succeeded. " : "failed. ") + "\"pod install\" was then attempted again, and still " + - "failed. This may be due to a broken Cocoapods " + + "failed. This may be due to a broken CocoaPods " + "installation. See: " + "https://guides.cocoapods.org/using/troubleshooting.html" + "for potential solutions.\n\n" + @@ -860,32 +2833,23 @@ public static void OnPostProcessInstallPods(BuildTarget buildTarget, } } - // Get a list of files relative to the specified directory matching the - // specified set of extensions. - internal static List FindFilesWithExtensions( - string directory, HashSet extensions) { - var outputList = new List(); - foreach (string subdir in Directory.GetDirectories(directory)) { - outputList.AddRange(FindFilesWithExtensions(subdir, extensions)); - } - foreach (string filename in Directory.GetFiles(directory)) { - string extension = Path.GetExtension(filename); - if (extensions.Contains(extension)) outputList.Add(filename); - } - return outputList; - } - /// - /// Finds the frameworks downloaded by cocoapods in the Pods directory + /// Finds the frameworks downloaded by CocoaPods in the Pods directory /// and adds them to the project. /// [PostProcessBuildAttribute(BUILD_ORDER_UPDATE_DEPS)] public static void OnPostProcessUpdateProjectDeps( BuildTarget buildTarget, string pathToBuiltProject) { - if (!InjectDependencies()) return; + if (!InjectDependencies() || !PodfileGenerationEnabled || + !CocoapodsProjectIntegrationEnabled || // Early out for Workspace level integration. + !cocoapodsToolsInstallPresent) { + return; + } + UpdateProjectDeps(buildTarget, pathToBuiltProject); } + // Handles the Xcode project level integration injection of scanned dependencies. // Implementation of OnPostProcessUpdateProjectDeps(). // NOTE: This is separate from the post-processing method to prevent the // Mono runtime from loading the Xcode API before calling the post @@ -896,204 +2860,58 @@ 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 + // and warn the user about it. + // We'll be taking the dependencies we scraped from the podfile and inserting them in the + // project with this method anyway, so nothing should be lost. + string workspacePath = Path.Combine(pathToBuiltProject, "Unity-iPhone.xcworkspace"); + if (UnityCanLoadWorkspace && CocoapodsProjectIntegrationEnabled && + Directory.Exists(workspacePath)) { + Log("Removing the generated workspace to force Unity to directly load the " + + "xcodeproj.\nSince Unity 5.6, Unity can now load workspace files generated " + + "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 > External Dependency Manager > " + + "iOS Resolver > Settings, using the CocoaPods Integration drop down menu.", + level: LogLevel.Warning); + Directory.Delete(workspacePath, true); + } - Directory.CreateDirectory(Path.Combine(pathToBuiltProject, - "Frameworks")); - Directory.CreateDirectory(Path.Combine(pathToBuiltProject, - "Resources")); - - string pbxprojPath = GetProjectPath(pathToBuiltProject); + var pbxprojPath = GetProjectPath(pathToBuiltProject); var project = new UnityEditor.iOS.Xcode.PBXProject(); project.ReadFromString(File.ReadAllText(pbxprojPath)); - string target = project.TargetGuidByName(TARGET_NAME); - - HashSet frameworks = new HashSet(); - HashSet linkFlags = new HashSet(); - foreach (var frameworkFullPath in - Directory.GetDirectories(podsDir, "*.framework", - SearchOption.AllDirectories)) { - string frameworkName = new DirectoryInfo(frameworkFullPath).Name; - string destFrameworkPath = Path.Combine("Frameworks", - frameworkName); - string destFrameworkFullPath = Path.Combine(pathToBuiltProject, - destFrameworkPath); - // Only move this framework if it contains a library. - // Skip frameworks that consist of just resources, they're handled - // in a separate import step. - if (!File.Exists(Path.Combine( - frameworkFullPath, - Path.GetFileName(frameworkFullPath) - .Replace(".framework", "")))) { - continue; - } - - PlayServicesSupport.DeleteExistingFileOrDirectory( - destFrameworkFullPath); - Directory.Move(frameworkFullPath, destFrameworkFullPath); - project.AddFileToBuild( - target, - project.AddFile(destFrameworkPath, - destFrameworkPath, - UnityEditor.iOS.Xcode.PBXSourceTree.Source)); - - string moduleMapPath = - Path.Combine(Path.Combine(destFrameworkFullPath, "Modules"), - "module.modulemap"); - - if (File.Exists(moduleMapPath)) { - // Parse the modulemap, format spec here: - // http://clang.llvm.org/docs/Modules.html#module-map-language - using (StreamReader moduleMapFile = - new StreamReader(moduleMapPath)) { - string line; - char[] delim = {' '}; - while ((line = moduleMapFile.ReadLine()) != null) { - string[] items = line.TrimStart(delim).Split(delim, 2); - if (items.Length > 1) { - if (items[0] == "link") { - if (items[1].StartsWith("framework")) { - items = items[1].Split(delim, 2); - frameworks.Add(items[1].Trim( - new char[] {'\"'}) + ".framework"); - } else { - linkFlags.Add("-l" + items[1]); - } - } - } - } - } - } - - string resourcesFolder = Path.Combine(destFrameworkFullPath, - "Resources"); - if (Directory.Exists(resourcesFolder)) { - string[] resFiles = Directory.GetFiles(resourcesFolder); - string[] resFolders = - Directory.GetDirectories(resourcesFolder); - foreach (var resFile in resFiles) { - string destFile = Path.Combine("Resources", - Path.GetFileName(resFile)); - File.Copy(resFile, Path.Combine(pathToBuiltProject, - destFile), true); - project.AddFileToBuild( - target, project.AddFile( - destFile, destFile, - UnityEditor.iOS.Xcode.PBXSourceTree.Source)); - } - foreach (var resFolder in resFolders) { - string destFolder = - Path.Combine("Resources", - new DirectoryInfo(resFolder).Name); - string destFolderFullPath = - Path.Combine(pathToBuiltProject, destFolder); - PlayServicesSupport.DeleteExistingFileOrDirectory( - destFolderFullPath); - Directory.Move(resFolder, destFolderFullPath); - project.AddFileToBuild( - target, project.AddFile( - destFolder, destFolder, - UnityEditor.iOS.Xcode.PBXSourceTree.Source)); - } - } - } - - foreach (var framework in frameworks) { - project.AddFrameworkToProject(target, framework, false); - } - foreach (var linkFlag in linkFlags) { - project.AddBuildProperty(target, "OTHER_LDFLAGS", linkFlag); - } - - // Add all source files found under the pods directory to the project. - // This is a very crude way of partially supporting source pods. - var podPathToProjectPaths = new Dictionary(); - // Find pod source files and map them to paths relative to the target - // Xcode project. - foreach (var filename in - FindFilesWithExtensions(podsDir, SOURCE_FILE_EXTENSIONS)) { - // Save the path relative to the target project for each path - // relative to the generated pods Xcode project. - // +1 in the following expressions to strip the file separator. - podPathToProjectPaths[filename.Substring(podsDir.Length + 1)] = - filename.Substring(pathToBuiltProject.Length + 1); - } - // Add a reference to each source file in the target project. - foreach (var podPathProjectPath in podPathToProjectPaths) { - project.AddFileToBuild( - target, - project.AddFile(podPathProjectPath.Value, - podPathProjectPath.Value, - UnityEditor.iOS.Xcode.PBXSourceTree.Source)); + 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); + } - // Attempt to read per-file compile / build settings from the Pods - // project. - var podsProjectPath = GetProjectPath(podsDir, PODS_PROJECT_NAME); - if (File.Exists(podsProjectPath)) { - var podsProject = new UnityEditor.iOS.Xcode.PBXProject(); - podsProject.ReadFromString(File.ReadAllText(podsProjectPath)); - foreach (var directory in Directory.GetDirectories(podsDir)) { - // Each pod will have a top level directory under the pods dir - // named after the pod. Also, some pods have build targets in - // the xcode project where each build target has the same name - // as the pod such that pod Foo is in directory Foo with build - // target Foo. Since we can't read the build targets from the - // generated Xcode project using Unity's API, we scan the Xcode - // project for targets to optionally retrieve build settings - // for each source file the settings can be applied in the - // target project. - var podTargetName = Path.GetFileName(directory); - var podTargetGuid = - podsProject.TargetGuidByName(podTargetName); - Log(String.Format("Looking for target: {0} guid: {1}", - podTargetName, podTargetGuid ?? "null"), - verbose: true); - if (podTargetGuid == null) continue; - foreach (var podPathProjectPath in podPathToProjectPaths) { - var podSourceFileGuid = podsProject.FindFileGuidByRealPath( - podPathProjectPath.Key); - if (podSourceFileGuid == null) continue; - var podSourceFileCompileFlags = - podsProject.GetCompileFlagsForFile(podTargetGuid, - podSourceFileGuid); - if (podSourceFileCompileFlags == null) { - continue; - } - var targetSourceFileGuid = - project.FindFileGuidByProjectPath( - podPathProjectPath.Value); - if (targetSourceFileGuid == null) { - Log("Unable to find " + podPathProjectPath.Value + - " in generated project", level: LogLevel.Warning); - continue; - } - Log(String.Format( - "Setting {0} compile flags to ({1})", - podPathProjectPath.Key, - String.Join(", ", - podSourceFileCompileFlags.ToArray())), - verbose: true); - project.SetCompileFlagsForFile( - target, targetSourceFileGuid, - podSourceFileCompileFlags); - } + /// + /// Read XML dependencies if the plugin is enabled. + /// + private static void RefreshXmlDependencies() { + // Remove all pods that were added via XML dependencies. + var podsToRemove = new List(); + foreach (var podNameAndPod in pods) { + if (podNameAndPod.Value.fromXmlFile) { + podsToRemove.Add(podNameAndPod.Key); } - } else if (File.Exists(podsProjectPath + ".xml")) { - // If neither the Pod pbxproj or pbxproj.xml are present pod - // install failed earlier and an error has already been report. - Log("Old Cocoapods installation detected (version: " + - podsVersion + "). Unable to include " + - "source pods, your project will not build.\n" + - "\n" + - "Older versions of the pod tool generate xml format Xcode " + - "projects which can not be read by Unity's xcodeapi. To " + - "resolve this issue update Cocoapods to at least version " + - "1.1.0\n\n" + - COCOAPOD_INSTALL_INSTRUCTIONS, - level: LogLevel.Error); } - - File.WriteAllText(pbxprojPath, project.WriteToString()); + foreach (var podName in podsToRemove) { + pods.Remove(podName); + } + // Clear all sources (only can be set via XML config). + Pod.Sources = new List>(); + // Read pod specifications from XML dependencies. + xmlDependencies.ReadAll(logger); } } diff --git a/source/IOSResolver/src/IOSResolverSettingsDialog.cs b/source/IOSResolver/src/IOSResolverSettingsDialog.cs new file mode 100644 index 00000000..e8f7001d --- /dev/null +++ b/source/IOSResolver/src/IOSResolverSettingsDialog.cs @@ -0,0 +1,377 @@ +// +// Copyright (C) 2017 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 UnityEditor; +using UnityEngine; + +/// +/// Settings dialog for IOSResolver. +/// +public class IOSResolverSettingsDialog : EditorWindow +{ + /// + /// Loads / saves settings for this dialog. + /// + private class Settings { + 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() { + 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); + } + + /// + /// Save dialog settings to preferences. + /// + internal void Save() { + IOSResolver.PodfileGenerationEnabled = podfileGenerationEnabled; + 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(); + } + } + + private Settings settings; + + static string[] cocopodsIntegrationStrings = new string[] { + "Xcode Workspace - Add Cocoapods to the Xcode workspace", + "Xcode Project - Add Cocoapods to the Xcode project", + "None - Do not integrate Cocoapods.", + }; + + // Menu item index to enum. + private static IOSResolver.CocoapodsIntegrationMethod[] integrationMapping = + new IOSResolver.CocoapodsIntegrationMethod[] { + IOSResolver.CocoapodsIntegrationMethod.Workspace, + IOSResolver.CocoapodsIntegrationMethod.Project, + 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( + IOSResolver.CocoapodsIntegrationMethod enumToFind) { + for (int i = 0; i < integrationMapping.Length; i++) { + if (integrationMapping[i] == enumToFind) return i; + } + throw new System.ArgumentException("Invalid CocoapodsIntegrationMethod."); + } + + public void Initialize() { + minSize = new Vector2(400, 715); + position = new Rect(UnityEngine.Screen.width / 3, UnityEngine.Screen.height / 3, + minSize.x, minSize.y); + } + + /// + /// Load settings for this dialog. + /// + private 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("iOS Resolver (version {0}.{1}.{2})", + IOSResolverVersionNumber.Value.Major, + IOSResolverVersionNumber.Value.Minor, + IOSResolverVersionNumber.Value.Build)); + + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Podfile Generation", EditorStyles.boldLabel); + settings.podfileGenerationEnabled = + EditorGUILayout.Toggle(settings.podfileGenerationEnabled); + GUILayout.EndHorizontal(); + GUILayout.Label("Podfile generation is required to install Cocoapods. " + + "It may be desirable to disable Podfile generation if frameworks " + + "are manually included in Unity's generated Xcode project."); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Cocoapods Integration", EditorStyles.boldLabel); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + settings.cocoapodsIntegrationMenuIndex = EditorGUILayout.Popup( + settings.cocoapodsIntegrationMenuIndex, cocopodsIntegrationStrings); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + if (integrationMapping[settings.cocoapodsIntegrationMenuIndex] != + IOSResolver.CocoapodsIntegrationMethod.None && !settings.podfileGenerationEnabled) { + GUILayout.Label("Cocoapod installation requires Podfile generation to be enabled."); + } else if (integrationMapping[settings.cocoapodsIntegrationMenuIndex] == + IOSResolver.CocoapodsIntegrationMethod.Workspace) { + GUILayout.Label("Unity Cloud Build and Unity 5.5 and below do not open generated " + + "Xcode workspaces so this plugin will fall back to Xcode Project " + + "integration in those environments."); + } + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Use Shell to Execute Cocoapod Tool", EditorStyles.boldLabel); + 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.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(); + GUILayout.Label("Auto Install Cocoapod Tools in Editor", EditorStyles.boldLabel); + settings.autoPodToolInstallInEditorEnabled = + EditorGUILayout.Toggle(settings.autoPodToolInstallInEditorEnabled); + GUILayout.EndHorizontal(); + if (settings.autoPodToolInstallInEditorEnabled) { + 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 > 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); + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + GUILayout.Label("Use project settings", EditorStyles.boldLabel); + settings.useProjectSettings = EditorGUILayout.Toggle(settings.useProjectSettings); + GUILayout.EndHorizontal(); + + 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) { + 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(); + } +} + +} // namespace Google + diff --git a/source/JarResolverLib/src/Google.JarResolver/ResolutionException.cs b/source/IOSResolver/src/VersionNumber.cs similarity index 51% rename from source/JarResolverLib/src/Google.JarResolver/ResolutionException.cs rename to source/IOSResolver/src/VersionNumber.cs index 8259728e..c9f3afa8 100644 --- a/source/JarResolverLib/src/Google.JarResolver/ResolutionException.cs +++ b/source/IOSResolver/src/VersionNumber.cs @@ -1,5 +1,5 @@ -// -// Copyright (C) 2014 Google Inc. All Rights Reserved. +// +// 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. @@ -14,23 +14,29 @@ // limitations under the License. // -namespace Google.JarResolver -{ +namespace Google { using System; + using UnityEditor; + /// - /// Resolution exception. This is a checked exception for resolution problems. - /// (you can take the developer out of java, but not the java out of the developer). + /// Get the version number of this plugin. /// - public class ResolutionException : Exception - { + public class IOSResolverVersionNumber { + + /// + /// 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); + /// - /// Initializes a new instance of the class. + /// Get the version number. /// - /// Message of the exception. - public ResolutionException(string msg) - : base(msg) - { - } + public static Version Value { get { return value; } } } } 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 163b5d70..00000000 --- a/source/JarResolver.sln +++ /dev/null @@ -1,233 +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", "{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 - {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 a3225fb6..2c43e509 100644 --- a/source/JarResolverLib/JarResolverLib.csproj +++ b/source/JarResolverLib/JarResolverLib.csproj @@ -23,7 +23,8 @@ False - none + True + full True bin\Release prompt @@ -31,6 +32,12 @@ False + + $(UnityHintPath)/UnityEditor.dll + + + $(UnityHintPath)/UnityEngine.dll + @@ -40,7 +47,10 @@ - + + + + diff --git a/source/JarResolverLib/src/Google.JarResolver/Dependency.cs b/source/JarResolverLib/src/Google.JarResolver/Dependency.cs index fbb8c9c3..f740e9c5 100644 --- a/source/JarResolverLib/src/Google.JarResolver/Dependency.cs +++ b/source/JarResolverLib/src/Google.JarResolver/Dependency.cs @@ -14,25 +14,19 @@ // limitations under the License. // -namespace Google.JarResolver -{ +namespace Google.JarResolver { using System.Collections.Generic; using System.Collections.ObjectModel; - using System.IO; + using System; /// /// Represents a dependency. A dependency is defined by a groupId, /// artifactId and version constraint. This information is used to search /// the repositories of artifacts to find a version that meets the version - /// contraints (as well as be compatible with other dependencies' constraints). - /// - /// Once the version is identified, the BestVersion property is used to get - /// the concrete version number that should be used. - /// + /// constraints (as well as be compatible with other dependencies' constraints). /// - public class Dependency - { - // TODO(wilkinsonclay): get the extension from the pom file. + public class Dependency { + // Extensions of files managed by the resolver. internal static string[] Packaging = { ".aar", ".jar", @@ -46,13 +40,7 @@ public class Dependency /// The version comparator. This comparator results in a descending sort /// order by version. /// - private readonly VersionComparer versionComparison = new VersionComparer(); - - /// - /// The possible versions found in the repository. This list is mutable - /// and will change as the constraints are applied. - /// - private List possibleVersions; + internal static readonly VersionComparer versionComparer = new VersionComparer(); /// /// Initializes a new instance of the @@ -61,24 +49,73 @@ 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. - public Dependency(string group, string artifact, string version, string[] packageIds=null, - string[] repositories=null) - { + /// Human readable string that describes where this dependency + /// originated. + 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 + // dependency was injected. + if (createdBy == null) { + var usefulFrames = new List(); + bool filterFrames = true; + // Filter all of the initial stack frames from system libraries and this plugin. + foreach (var frame in System.Environment.StackTrace.Split(new char[] { '\n' })) { + var frameString = frame.Trim(); + if (frameString.StartsWith("at ")) frameString = frameString.Split()[1]; + if (filterFrames && ( + frameString.StartsWith("System.Environment.") || + frameString.StartsWith("Google.JarResolver.") || + frameString.StartsWith("System.Reflection.") || + frameString.StartsWith("Google.VersionHandler"))) { + 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()); + } Group = group; Artifact = artifact; Version = version; + Classifier = classifier; PackageIds = packageIds; - this.possibleVersions = new List(); Repositories = repositories; - CreatedBy = System.Environment.StackTrace; + CreatedBy = createdBy; + } + + /// + /// Copy Dependency. + /// + /// Dependency to copy. + 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(); + } + if (dependency.Repositories != null) { + Repositories = (string[])dependency.Repositories.Clone(); + } + CreatedBy = dependency.CreatedBy; } /// - /// Stack trace of the point where this was created. + /// Tag that indicates where this was created. /// internal string CreatedBy { get; private set; } @@ -98,7 +135,15 @@ public Dependency(string group, string artifact, string version, string[] packag /// Gets the version constraint. /// /// The version. - public string Version { get; private set; } + 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 @@ -113,239 +158,23 @@ public Dependency(string group, string artifact, string version, string[] packag /// List of repository directories if set or null. public string[] Repositories { get; private set; } - /// - /// Gets the best version based on the version contraint, other - /// dependencies (if resolve has been run), and the availability of the - /// artifacts in the repository. If this value is null or empty, either - /// it has not been initialized by calling - /// PlayServicesSupport.AddDependency() - /// or there are no versions that meet all the constraints. - /// - /// The best version. - public string BestVersion - { - get - { - if (possibleVersions.Count > 0) - { - return possibleVersions[0]; - } - else - { - return string.Empty; - } - } - } - - /// - /// Returns the available versions of this dependency. - /// - public ReadOnlyCollection PossibleVersions - { - get - { - return possibleVersions.AsReadOnly(); - } - } - - /// - /// Gets the best version path relative to the SDK. - /// - /// The best version path. - public string BestVersionPath - { - get - { - if (!string.IsNullOrEmpty(BestVersion)) - { - string path = Group + Path.DirectorySeparatorChar + - Artifact; - path = path.Replace('.', Path.DirectorySeparatorChar); - return RepoPath + Path.DirectorySeparatorChar + path + - Path.DirectorySeparatorChar + BestVersion; - } - - return string.Empty; - } - } - - /// - /// Get the path to the artifact. - /// - public string BestVersionArtifact - { - get - { - // TODO(wilkinsonclay): get the extension from the pom file. - string filenameWithoutExtension = - Path.Combine(BestVersionPath, Artifact + "-" + BestVersion); - foreach (string extension in Packaging) - { - string filename = filenameWithoutExtension + extension; - if (File.Exists(filename)) return filename; - } - return null; - } - } - - /// - /// Gets or sets the repository path for this dependency. This is - /// relative to the SDK. - /// - /// The repo path. - public string RepoPath { get; set; } - /// /// Gets the versionless key. This key is used to manage collections /// of dependencies, regardless of the version constraint. /// /// The versionless key. - public string VersionlessKey - { - get - { - return Group + ":" + Artifact; - } - } + public string VersionlessKey { get { return Group + ":" + Artifact; } } /// /// Gets the key for this dependency. The key is a tuple of the /// group, artifact and version constraint. /// /// The key. - public string Key - { - get - { - return Group + ":" + Artifact + ":" + Version; - } - } - - /// - /// Gets a value indicating whether this instance has possible versions. - /// - /// true if this instance has possible versions. - public bool HasPossibleVersions - { - get - { - return possibleVersions.Count > 0; - } - } - - /// - /// Determines whether this instance is newer the specified candidate. - /// - /// true - /// if this instance is newer the specified candidate. - /// Candidate to test - public bool IsNewer(Dependency candidate) - { - if (candidate.Group == Group && candidate.Artifact == Artifact) - { - return IsGreater(Version, candidate.Version); - } - - return false; - } - - /// - /// Determines whether this instance is acceptable based - /// on the version constraint. - /// - /// true if this instance is acceptable - /// version the specified ver. - /// Version to check. - public bool IsAcceptableVersion(string ver) - { - bool hasPlus = Version.Contains("+"); - bool latest = Version.ToUpper().Equals("LATEST"); - if (latest) - { - return string.IsNullOrEmpty(BestVersion) || - IsGreater(ver, BestVersion); - } - - if (!hasPlus) - { - if (ver.Equals(Version)) - { - return true; - } - else - { - string[] myParts = Version.Split('.'); - string[] parts = ver.Split('.'); - return AreEquivalent(myParts, parts); - } - } - else - { - string[] myParts = Version.Split('.'); - string[] parts = ver.Split('.'); - return IsAcceptable(myParts, parts); - } - } - - /// - /// Refines the possible version range based on the given candidate. - /// This is done by removing possible versions that are not acceptable - /// to the candidate. - /// - /// true, if there are still possible versions. - /// Candidate to test versions with. - public bool RefineVersionRange(Dependency candidate) - { - // remove all possible versions that are not acceptable to the - // candidate - List removals = new List(); - - // add the possible versions to both, so the sets are the same. - foreach (string v in possibleVersions) - { - if (!candidate.IsAcceptableVersion(v)) - { - removals.Add(v); - } - } - - foreach (string v in removals) - { - possibleVersions.Remove(v); - } - - return HasPossibleVersions; - } - - /// - /// Removes the possible version. - /// - /// Ver to remove. - public void RemovePossibleVersion(string ver) - { - possibleVersions.Remove(ver); - } - - /// - /// Adds the version as possible version if acceptable. - /// Acceptable versions meet the version constraint and are not already in - /// the list. - /// - /// Version to add - public void AddVersion(string ver) - { - if (possibleVersions.Contains(ver)) - { - return; - } - - if (IsAcceptableVersion(ver)) - { - possibleVersions.Add(ver); - } - - possibleVersions.Sort(versionComparison); - } + public string Key { get { + string key = Group + ":" + Artifact + ":" + Version; + if (!String.IsNullOrEmpty(Classifier)) + key += ":" + Classifier; + return key; } } /// /// Returns a that represents @@ -353,10 +182,7 @@ public void AddVersion(string ver) /// /// A that represents the /// current . - public override string ToString() - { - return Key + "(" + BestVersion + ")"; - } + public override string ToString() { return Key; } /// /// Determines if version1 is greater than version2 @@ -364,251 +190,43 @@ public override string ToString() /// true if version1 is greater than version2. /// Version1 to test. /// Version2 to test. - internal static bool IsGreater(string version1, string version2) - { - // only works for concrete versions so remove "+" - string[] parts1; - string[] parts2; - if (version1.EndsWith("+")) - { - parts1 = version1.Substring(0, version1.Length - 1).Split('.'); - } - else - { - parts1 = version1.Split('.'); - } - - if (version2.EndsWith("+")) - { - parts2 = version2.Substring(0, version2.Length - 1).Split('.'); - } - else - { - parts2 = version2.Split('.'); - } - - int i1 = 0; - int i2 = 0; - - while (i1 < parts1.Length || i2 < parts2.Length) - { - int val1 = -1; - int val2 = -1; - if (i1 < parts1.Length) - { - if (!int.TryParse(parts1[i1], out val1)) - { - // -1 means use string compare - val1 = -1; + private static bool IsGreater(string version1, string version2) { + version1 = version1.EndsWith("+") ? + version1.Substring(0, version1.Length - 1) : version1; + version2 = version2.EndsWith("+") ? + version2.Substring(0, version2.Length - 1) : version2; + string[] version1Components = version1.Split('.'); + string[] version2Components = version2.Split('.'); + int componentsToCompare = Math.Min(version1Components.Length, + version2Components.Length); + for (int i = 0; i < componentsToCompare; ++i) { + string version1Component = version1Components[i]; + int version1ComponentInt; + string version2Component = version2Components[i]; + int version2ComponentInt; + if (Int32.TryParse(version1Component, out version1ComponentInt) && + Int32.TryParse(version2Component, out version2ComponentInt)) { + if (version1ComponentInt > version2ComponentInt) { + return true; + } else if (version1ComponentInt < version2ComponentInt) { + return false; } - } - else - { - val1 = -2; - } - - if (i2 < parts2.Length) - { - if (!int.TryParse(parts2[i2], out val2)) - { - // -1 means use string compare - val2 = -1; + } else { + int stringCompareResult = version1Component.CompareTo(version2Component); + if (stringCompareResult > 0) { + return true; + } else if (stringCompareResult < 0) { + return false; } } - else - { - val2 = -3; - } - - if (val1 != val2 || val1 < 0) - { - if (val1 == -1) - { - if (val2 >= -1) - { - return parts1[i1].CompareTo(parts2[i2]) > 0; - } - else - { - // parts2 is shorter, so parts1 wins. - return true; - } - } - else if (val2 == -1) - { - if (val1 >= -1) - { - return parts1[i1].CompareTo(parts2[i2]) > 0; - } - else - { - // parts1 is shorter, so parts2 wins. - return false; - } - } - else - { - return val1 > val2; - } - } - - i1++; - i2++; } - - return false; - } - - /// - /// Determines whether version 2 meets the constraints of version 1 - /// - /// true if ver2 is acceptable to ver1. - /// Version 1 - /// Version 2 - internal bool IsAcceptable(string[] ver1, string[] ver2) - { - int i1 = 0; - int i2 = 0; - bool sawPlus = false; - while (i1 < ver1.Length || i2 < ver2.Length) - { - int val1 = -1; - int val2 = -1; - - // check if v1 has the + at this index. - if (i1 >= ver1.Length) - { - // use the wildcard to extend? - throw new System.NotImplementedException(); - } - else if (ver1[i1].Contains("+")) - { - sawPlus = true; - string v = ver1[i1].Substring(0, ver1[i1].IndexOf('+')); - if (!int.TryParse(v, out val1)) - { - if (string.IsNullOrEmpty(v)) - { - val1 = 0; - } - else - { - // -1 means use string compare - val1 = -1; - } - } - } - else - { - // straight comparison - if (!int.TryParse(ver1[i1], out val1)) - { - // -1 means use string compare - val1 = -1; - } - } - - if (i2 >= ver2.Length) - { - return false; - } - else - { - // straight comparison - if (!int.TryParse(ver2[i2], out val2)) - { - // -1 means use string compare - val2 = -1; - } - } - - if ((val1 == -1 || val2 == -1) && - !ver1[i1].ToLower().Equals(ver2[i2].ToLower())) - { - return false; - } - else if (val1 != val2 && !sawPlus) - { - return false; - } - else if (sawPlus) - { - return val1 <= val2; - } - - i1++; - i2++; - } - - return true; - } - - /// - /// Checks if ours version (stored as an array) is equivalent to theirs. - /// Equivalency handles identifying version constraints (ours) that - /// do not have the same number of trailing zeros as a version (theirs). - /// - /// true, if equivalent false otherwise. - /// our version parsed into an array. - /// Theirs parsed into an array. - internal bool AreEquivalent(string[] ours, string[] theirs) - { - int ourIndex = 0; - int theirIndex = 0; - while (ourIndex < ours.Length || theirIndex < theirs.Length) - { - int us = -1; - int them = -1; - if (ourIndex < ours.Length) - { - if (!int.TryParse(ours[ourIndex], out us)) - { - // -1 means use string compare - us = -1; - } - } - else - { - us = 0; - } - - if (theirIndex < theirs.Length) - { - if (!int.TryParse(theirs[theirIndex], out them)) - { - // -1 means use string compare - them = -1; - } - } - else - { - them = 0; - } - - if (us >= 0 && them >= 0 && us != them) - { - return false; - } - else if ((us < 0 || them < 0) && - !ours[ourIndex].ToLower().Equals(theirs[theirIndex].ToLower())) - { - return false; - } - - ourIndex++; - theirIndex++; - } - - return true; + return version1Components.Length > version2Components.Length; } /// /// Version comparer. Resulting in a descending list of versions. /// - public class VersionComparer : IComparer - { - #region IComparer implementation - + public class VersionComparer : IComparer { /// /// Compare the specified x and y. /// @@ -616,23 +234,14 @@ public class VersionComparer : IComparer /// The y coordinate. /// negative if x is greater than y, /// positive if y is greater than x, 0 if equal. - public int Compare(string x, string y) - { - if (IsGreater(x, y)) - { + public int Compare(string x, string y) { + if (IsGreater(x, y)) { return -1; - } - else if (IsGreater(y, x)) - { + } else if (IsGreater(y, x)) { return 1; } - else - { - return 0; - } + return 0; } - - #endregion } } } diff --git a/source/JarResolverLib/src/Google.JarResolver/PlayServicesSupport.cs b/source/JarResolverLib/src/Google.JarResolver/PlayServicesSupport.cs index 99f570e9..673b8915 100644 --- a/source/JarResolverLib/src/Google.JarResolver/PlayServicesSupport.cs +++ b/source/JarResolverLib/src/Google.JarResolver/PlayServicesSupport.cs @@ -20,8 +20,11 @@ namespace Google.JarResolver using System.Diagnostics; using System.Collections.Generic; using System.IO; + using System.Text.RegularExpressions; using System.Xml; + using Google; + /// /// Play services support is a helper class for managing the Google play services /// and Android support libraries in a Unity project. This is done by using @@ -38,9 +41,13 @@ public class PlayServicesSupport private string clientName; /// - /// The path to the Android SDK. + /// Log severity. /// - private static string sdk; + public enum LogLevel { + Info, + Warning, + Error, + }; /// /// Delegate used to specify a log method for this class. If provided this class @@ -48,16 +55,48 @@ public class PlayServicesSupport /// public delegate void LogMessage(string message); + /// + /// Delegate used to specify a log method for this class. If provided this class + /// will log messages via this delegate. + /// + /// Message to log. + /// Severity of the log message. + public delegate void LogMessageWithLevel(string message, LogLevel level); + /// /// Log function delegate. If set, this class will write log messages via this method. /// - internal static LogMessage logger; + internal static LogMessageWithLevel logger; /// /// The repository paths. /// private List repositoryPaths = new List(); + + /// + /// List of additional global repository paths. + /// These are added to the set of repositories used to construct this class. + /// The key in the pair is the repo and the value is the source (file & line) it was + /// parsed from. + /// + internal static List> AdditionalRepositoryPaths = + new List>(); + + /// + /// Get the set of repository paths. + /// This includes the repo paths specified at construction and AdditionalRepositoryPaths. + /// + internal List RepositoryPaths { + get { + var allPaths = new List(repositoryPaths); + foreach (var kv in AdditionalRepositoryPaths) { + allPaths.Add(kv.Value); + } + return allPaths; + } + } + /// /// The client dependencies map. This is a proper subset of dependencyMap. /// @@ -67,12 +106,7 @@ public class PlayServicesSupport /// /// String that is expanded with the path of the Android SDK. /// - private const string SdkVariable = "$SDK"; - - /// - /// Extension of Unity metadata files. - /// - internal const string MetaExtension = ".meta"; + internal const string SdkVariable = "$SDK"; /// /// Error message displayed / logged when the Android SDK path isn't configured. @@ -84,44 +118,19 @@ public class PlayServicesSupport "or the \"Unity > Preferences > External Tools\" menu option on OSX. " + "Alternatively, set the ANDROID_HOME environment variable"); - /// - /// Delegate used to prompt or notify the developer that an existing - /// dependency file is being overwritten. - /// - /// The dependency being replaced - /// The dependency to use - /// If true the replacement is performed. - public delegate bool OverwriteConfirmation(Dependency oldDep, Dependency newDep); - - /// - /// Delegate used to determine whether an AAR should be exploded. - /// - /// Path to the AAR file to examine. - /// True if the AAR should be exploded, false otherwise. - public delegate bool ExplodeAar(string aarPath); - - private static string SDKInternal - { - get - { -#if UNITY_EDITOR - if (String.IsNullOrEmpty(sdk)) { - sdk = UnityEditor.EditorPrefs.GetString("AndroidSdkRoot"); - } -#endif // UNITY_EDITOR - if (string.IsNullOrEmpty(sdk)) { - sdk = System.Environment.GetEnvironmentVariable("ANDROID_HOME"); - } - return sdk; - } - } - - /// - /// Gets the Android SDK. If it is not set, the environment - /// variable ANDROID_HOME is used. - /// - /// The SD. - public string SDK { get { return PlayServicesSupport.SDKInternal; } } + // Map of common dependencies to Android SDK packages. + private static List> CommonPackages = + new List> { + new KeyValuePair( + new Regex("^com\\.android\\.support:support-.*"), + "extra-android-m2repository"), + new KeyValuePair( + new Regex("^com\\.google\\.android\\.gms:.*"), + "extra-google-m2repository"), + new KeyValuePair( + new Regex("^com\\.google\\.firebase:firebase-.*"), + "extra-google-m2repository") + }; /// /// Whether verbose logging is enabled. @@ -141,16 +150,16 @@ private static string SDKInternal /// This is used to uniquely identify /// the calling client so that dependencies can be associated with a specific /// client to help in resetting dependencies. - /// Sdk path for Android SDK. + /// Sdk path for Android SDK (unused). /// This parameter is obsolete. /// Delegate used to write messages to the log. + /// Delegate used to write messages to the log. If + /// this is specified "logger" is ignored. public static PlayServicesSupport CreateInstance( - string clientName, - string sdkPath, - string settingsDirectory, - LogMessage logger = null) - { - return CreateInstance(clientName, sdkPath, null, settingsDirectory, logger: logger); + string clientName, string sdkPath, string settingsDirectory, + LogMessage logger = null, LogMessageWithLevel logMessageWithLevel = null) { + return CreateInstance(clientName, sdkPath, null, settingsDirectory, logger: logger, + logMessageWithLevel: logMessageWithLevel); } /// @@ -162,22 +171,25 @@ public static PlayServicesSupport CreateInstance( /// This is used to uniquely identify /// the calling client so that dependencies can be associated with a specific /// client to help in resetting dependencies. - /// Sdk path for Android SDK. + /// Sdk path for Android SDK (unused). /// Array of additional repository paths. can be /// null /// This parameter is obsolete. /// Delegate used to write messages to the log. + /// Delegate used to write messages to the log. If + /// this is specified "logger" is ignored. internal static PlayServicesSupport CreateInstance( - string clientName, - string sdkPath, - string[] additionalRepositories, - string settingsDirectory, - LogMessage logger = null) + string clientName, string sdkPath, string[] additionalRepositories, + string settingsDirectory, LogMessage logger = null, + LogMessageWithLevel logMessageWithLevel = null) { PlayServicesSupport instance = new PlayServicesSupport(); - PlayServicesSupport.logger = PlayServicesSupport.logger ?? logger; - PlayServicesSupport.sdk = - String.IsNullOrEmpty(sdkPath) ? PlayServicesSupport.sdk : sdkPath; + LogMessageWithLevel legacyLogger = (string message, LogLevel level) => { + if (logger != null) logger(message); + }; + PlayServicesSupport.logger = + PlayServicesSupport.logger ?? (logMessageWithLevel ?? + (logger != null ? legacyLogger : null)); string badchars = new string(Path.GetInvalidFileNameChars()); foreach (char ch in clientName) @@ -187,6 +199,7 @@ internal static PlayServicesSupport CreateInstance( throw new Exception("Invalid clientName: " + clientName); } } + instance.clientName = clientName; var repoPaths = new List(); @@ -206,68 +219,36 @@ internal static PlayServicesSupport CreateInstance( /// /// Log a message to the currently set logger. /// - /// - /// - /// message is a string to write to the log. - /// - /// - internal static void Log(string message, bool verbose = false) { + /// Message to log. + /// Severity of the log message. + /// Whether the message should only be displayed with verbose + /// logging enabled. + internal static void Log(string message, LogLevel level = LogLevel.Info, + bool verbose = false) { if (logger != null && (!verbose || verboseLogging)) { - logger(message); + logger(message, level); } } /// - /// Delete a file or directory if it exists. + /// Lookup common package IDs for a dependency. /// - /// Path to the file or directory to delete if it exists. - public static void DeleteExistingFileOrDirectory(string path, - bool includeMetaFiles = false) - { - if (Directory.Exists(path)) - { - var di = new DirectoryInfo(path); - di.Attributes &= ~FileAttributes.ReadOnly; - foreach (string file in Directory.GetFileSystemEntries(path)) - { - DeleteExistingFileOrDirectory(file, includeMetaFiles: includeMetaFiles); - } - Directory.Delete(path); - } - else if (File.Exists(path)) - { - File.SetAttributes(path, File.GetAttributes(path) & ~FileAttributes.ReadOnly); - File.Delete(path); - } - if (includeMetaFiles && !path.EndsWith(MetaExtension)) - { - DeleteExistingFileOrDirectory(path + MetaExtension); - } - } - - /// - /// Copy the contents of a directory to another directory. - /// - /// Path to copy the contents from. - /// Path to copy to. - public static void CopyDirectory(string sourceDir, string targetDir) - { - Func sourceToTargetPath = (path) => { - return Path.Combine(targetDir, path.Substring(sourceDir.Length + 1)); - }; - foreach (string sourcePath in - Directory.GetDirectories(sourceDir, "*", SearchOption.AllDirectories)) - { - Directory.CreateDirectory(sourceToTargetPath(sourcePath)); - } - foreach (string sourcePath in - Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories)) - { - if (!sourcePath.EndsWith(PlayServicesSupport.MetaExtension)) - { - File.Copy(sourcePath, sourceToTargetPath(sourcePath)); + private static Dependency AddCommonPackageIds(Dependency dep) { + if (dep.PackageIds != null) return dep; + + var packageNames = new List(); + string[] packageIds = null; + foreach (var kv in CommonPackages) { + var match = kv.Key.Match(dep.Key); + if (match.Success) { + packageNames.Add(kv.Value); + break; } } + if (packageNames.Count > 0) packageIds = packageNames.ToArray(); + return new Dependency(dep.Group, dep.Artifact, dep.Version, + classifier: dep.Classifier, packageIds: packageIds, + repositories: dep.Repositories); } /// @@ -292,18 +273,23 @@ public static void CopyDirectory(string sourceDir, string targetDir) /// LATEST means the only the latest version. /// /// - /// Group - the Group Id of the artiface + /// 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 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: " + @@ -313,467 +299,46 @@ public void DependOn(string group, string artifact, string version, repositories = repositories ?? new string[] {}; var depRepoList = new List(repositories); depRepoList.AddRange(repositoryPaths); - var dep = new Dependency(group, artifact, version, - packageIds: packageIds, - repositories: UniqueList(depRepoList).ToArray()); + var dep = AddCommonPackageIds(new Dependency( + group, artifact, version, classifier: classifier, + packageIds: packageIds, + repositories: UniqueList(depRepoList).ToArray(), + createdBy: createdBy)); clientDependenciesMap[dep.Key] = dep; } /// - /// Clears the dependencies for this client. - /// - public void ClearDependencies() - { - clientDependenciesMap = new Dictionary(); - } - - /// - /// Get the current set of dependencies referenced by the project. - /// - /// Dependencies to search for in the specified - /// directory. - /// Directory where dependencies are located in the - /// project. - /// Delegate that determines whether a dependency should be - /// exploded. If a dependency is currently exploded but shouldn't be according to this - /// delegate, the dependency is deleted. - /// Set of additional repo paths to search for the - /// dependencies. - /// Dictionary indexed by Dependency.Key where each item is a tuple of - /// Dependency instance and the path to the dependency in destDirectory. - public static Dictionary> GetCurrentDependencies( - Dictionary dependencies, string destDirectory, - ExplodeAar explodeAar = null, List repoPaths = null) - { - var currentDependencies = new Dictionary>(); - if (dependencies.Count == 0) return currentDependencies; - - // Copy the set of dependencies. - var transitiveDependencies = new Dictionary(dependencies); - // Expand set of transitive dependencies into the dictionary of dependencies. - foreach (var rootDependency in dependencies.Values) - { - foreach (var transitiveDependency in GetDependencies(rootDependency, repoPaths)) - { - transitiveDependencies[transitiveDependency.Key] = transitiveDependency; - } - } - // TODO(smiles): Need a callback that queries Unity's asset DB rather than touching - // the filesystem here. - string[] filesInDestDir = Directory.GetFileSystemEntries(destDirectory); - foreach (var path in filesInDestDir) - { - // Ignore Unity's .meta files. - if (path.EndsWith(MetaExtension)) continue; - - string filename = Path.GetFileName(path); - // Strip the package extension from filenames. Directories generated from - // unpacked AARs do not have extensions. - bool pathIsDirectory = Directory.Exists(path); - string filenameWithoutExtension = pathIsDirectory ? filename : - Path.GetFileNameWithoutExtension(filename); - - foreach (var dep in transitiveDependencies.Values) - { - // Get the set of artifacts matching artifact-*. - // The "-" is important to distinguish art-1.0.0 from artifact-1.0.0 - // (or base- and basement-). - var match = System.Text.RegularExpressions.Regex.Match( - filenameWithoutExtension, String.Format("^{0}-.*", dep.Artifact)); - if (match.Success) - { - // Extract the version from the filename. - // dep.Artifact is the name of the package (prefix) - // The regular expression extracts the version number from the filename - // handling filenames like foo-1.2.3-alpha. - match = System.Text.RegularExpressions.Regex.Match( - filenameWithoutExtension.Substring( - dep.Artifact.Length + 1), "^([0-9.]+)"); - if (match.Success) - { - bool reportDependency = true; - // If the AAR is exploded and it should not be, delete it and do not - // report this dependency. - if (pathIsDirectory && explodeAar != null) - { - string aarFile = dep.BestVersionArtifact; - if (aarFile != null && !explodeAar(aarFile)) - { - DeleteExistingFileOrDirectory(path, - includeMetaFiles: true); - reportDependency = false; - } - } - if (reportDependency) - { - string artifactVersion = match.Groups[1].Value; - Dependency currentDep = new Dependency( - dep.Group, dep.Artifact, artifactVersion, - packageIds: dep.PackageIds, repositories: dep.Repositories); - // Add the artifact version so BestVersion == Version. - currentDep.AddVersion(currentDep.Version); - currentDependencies[currentDep.Key] = - new KeyValuePair(currentDep, path); - } - break; - } - } - } - } - return currentDependencies; - } - - /// - /// Determine whether two lists of strings match. + /// Get the current list of dependencies for all clients. /// - /// Enumerable of strings to compare.. - /// Enumerable of strings to compare.. - /// true if both enumerables match, false otherwise. - public static bool DependenciesEqual(IEnumerable deps1, - IEnumerable deps2) - { - var list1 = new List(deps1); - var list2 = new List(deps2); - list1.Sort(); - list2.Sort(); - if (list1.Count != list2.Count) return false; - for (int i = 0; i < list1.Count; ++i) - { - if (list1[i] != list2[i]) return false; - } - return true; - } - - /// - /// Determine whether the current set of artifacts in the project matches the set of - /// specified dependencies. - /// - /// Directory where dependencies are located in the - /// project. If this parameter is null, a dictionary of required dependencies is - /// always returned. - /// Delegate that determines whether a dependency should be - /// exploded. If a dependency is currently exploded but shouldn't be according to this - /// delegate, the dependency is deleted. - /// null if all dependencies are present, dictionary of all required dependencies - /// otherwise. - public Dictionary DependenciesPresent(string destDirectory, - ExplodeAar explodeAar = null) - { - Dictionary dependencyMap = - LoadDependencies(true, keepMissing: true, findCandidates: true); - // If a destination directory was specified, determine whether the dependencies - // referenced by dependencyMap differ to what is present in the project. If they - // are the same, we can skip this entire method. - if (destDirectory != null) - { - if (DependenciesEqual(GetCurrentDependencies(dependencyMap, destDirectory, - explodeAar: explodeAar, - repoPaths: repositoryPaths).Keys, - dependencyMap.Keys)) - { - Log("All dependencies up to date.", verbose: true); - return null; - } - } - return dependencyMap; - } - - /// - /// Performs the resolution process. This determines the versions of the - /// dependencies that should be used. Transitive dependencies are also - /// processed. - /// - /// The dependencies. The key is the "versionless" key of the dependency. - /// - /// If set to true use latest version of a conflicting - /// dependency. - /// if false a ResolutionException is thrown in the case of a conflict. - /// Directory dependencies will be copied to using - /// CopyDependencies(). - /// Delegate that determines whether a dependency should be - /// exploded. If a dependency is currently exploded but shouldn't be according to this - /// delegate, the dependency is deleted. - public Dictionary ResolveDependencies( - bool useLatest, string destDirectory = null, ExplodeAar explodeAar = null) - { - List unresolved = new List(); - - Dictionary candidates = new Dictionary(); - - Dictionary dependencyMap = - DependenciesPresent(destDirectory, explodeAar: explodeAar); - if (dependencyMap == null) return candidates; - - // Set of each versioned dependencies for each version-less dependency key. - // e.g if foo depends upon bar, map[bar] = {foo}. - var reverseDependencyTree = new Dictionary>(); - var warnings = new HashSet(); - - // All dependencies are added to the "unresolved" list. - foreach (var dependency in dependencyMap.Values) - { - unresolved.Add(FindCandidate(dependency)); - } - - do - { - Dictionary nextUnresolved = - new Dictionary(); - - foreach (Dependency dep in unresolved) - { - var currentDep = dep; - // Whether the dependency has been resolved and therefore should be removed - // from the unresolved list. - bool removeDep = true; - - // check for existing candidate - Dependency candidate; - Dependency newCandidate; - if (candidates.TryGetValue(currentDep.VersionlessKey, out candidate)) - { - if (currentDep.IsAcceptableVersion(candidate.BestVersion)) - { - removeDep = true; - - // save the most restrictive dependency in the - // candidate - if (currentDep.IsNewer(candidate)) - { - candidates[currentDep.VersionlessKey] = currentDep; - } - } - else - { - // in general, we need to iterate - removeDep = false; - - // refine one or both dependencies if they are - // non-concrete. - bool possible = false; - if (currentDep.Version.Contains("+") && candidate.IsNewer(currentDep)) - { - possible = currentDep.RefineVersionRange(candidate); - } - - // only try if the candidate is less than the depenceny - if (candidate.Version.Contains("+") && currentDep.IsNewer(candidate)) - { - possible = possible || candidate.RefineVersionRange(currentDep); - } - - if (possible) - { - // add all the dependency constraints back to make - // sure all are met. - foreach (Dependency d in dependencyMap.Values) - { - if (d.VersionlessKey == candidate.VersionlessKey) - { - if (!nextUnresolved.ContainsKey(d.Key)) - { - nextUnresolved.Add(d.Key, d); - } - } - } - } - else if (!possible && useLatest) - { - // Reload versions of the dependency has they all have been - // removed. - newCandidate = (currentDep.IsNewer(candidate) ? - currentDep : candidate); - newCandidate = newCandidate.HasPossibleVersions ? newCandidate : - FindCandidate(newCandidate); - candidates[newCandidate.VersionlessKey] = newCandidate; - currentDep = newCandidate; - removeDep = true; - // Due to a dependency being included via multiple modules we track - // whether a warning has already been reported and make sure it's - // only reported once. - if (!warnings.Contains(currentDep.VersionlessKey)) { - // If no parents of this dependency are found the app - // must have specified the dependency. - string requiredByString = - currentDep.VersionlessKey + " required by (this app)"; - // Print dependencies to aid debugging. - var dependenciesMessage = new List(); - dependenciesMessage.Add("Found dependencies:"); - dependenciesMessage.Add(requiredByString); - foreach (var kv in reverseDependencyTree) { - string requiredByMessage = - String.Format( - "{0} required by ({1})", - kv.Key, - String.Join( - ", ", - (new List(kv.Value)).ToArray())); - dependenciesMessage.Add(requiredByMessage); - if (kv.Key == currentDep.VersionlessKey) { - requiredByString = requiredByMessage; - } - } - Log(String.Join("\n", dependenciesMessage.ToArray())); - Log(String.Format( - "WARNING: No compatible versions of {0}, will try using " + - "the latest version {1}", requiredByString, - currentDep.BestVersion)); - warnings.Add(currentDep.VersionlessKey); - } - } - else if (!possible) - { - throw new ResolutionException("Cannot resolve " + - currentDep + " and " + candidate); - } - } - } - else - { - candidate = FindCandidate(currentDep); - if (candidate != null) - { - candidates.Add(candidate.VersionlessKey, candidate); - removeDep = true; - } - else - { - throw new ResolutionException("Cannot resolve " + - currentDep); - } - } - - // If the dependency has been found. - if (removeDep) - { - // Add all transitive dependencies to resolution list. - foreach (Dependency d in GetDependencies(currentDep)) - { - if (!nextUnresolved.ContainsKey(d.Key)) - { - Log("For " + currentDep.Key + " adding dep " + d.Key, - verbose: true); - HashSet parentNames; - if (!reverseDependencyTree.TryGetValue(d.VersionlessKey, - out parentNames)) { - parentNames = new HashSet(); - } - parentNames.Add(currentDep.Key); - reverseDependencyTree[d.VersionlessKey] = parentNames; - nextUnresolved.Add(d.Key, d); - } - } - } - else - { - if (!nextUnresolved.ContainsKey(currentDep.Key)) - { - nextUnresolved.Add(currentDep.Key, currentDep); - } - } + /// Dictionary of Dependency instances indexed by Dependency.Key. + public static Dictionary GetAllDependencies() { + var allDependencies = new Dictionary(); + foreach (var instance in instances.Values) { + foreach (var dependencyByKey in instance.clientDependenciesMap) { + allDependencies[dependencyByKey.Key] = new Dependency(dependencyByKey.Value); } - - unresolved.Clear(); - unresolved.AddRange(nextUnresolved.Values); - nextUnresolved.Clear(); } - while (unresolved.Count > 0); - - return candidates; + return allDependencies; } /// - /// Copies the dependencies from the repository to the specified directory. - /// The destination directory is checked for an existing version of the - /// dependency before copying. The OverwriteConfirmation delegate is - /// called for the first existing file or directory. If the delegate - /// returns true, the old dependency is deleted and the new one copied. + /// Clears the dependencies for this client. /// - /// The dependencies to copy. - /// Destination directory. - /// Confirmer - the delegate for confirming overwriting. - public void CopyDependencies( - Dictionary dependencies, - string destDirectory, - OverwriteConfirmation confirmer) + public void ClearDependencies() { - if (!Directory.Exists(destDirectory)) - { - Directory.CreateDirectory(destDirectory); - } - - // Build a dictionary of the source dependencies without the version in the key - // to simplify looking up dependencies currently in the project based upon filenames. - var currentDepsByVersionlessKey = - new Dictionary>(); - foreach (var item in GetCurrentDependencies(dependencies, destDirectory, - repoPaths: repositoryPaths)) - { - currentDepsByVersionlessKey[item.Value.Key.VersionlessKey] = item.Value; - } - - foreach (var dep in dependencies.Values) - { - KeyValuePair oldDepFilenamePair; - if (currentDepsByVersionlessKey.TryGetValue(dep.VersionlessKey, - out oldDepFilenamePair)) - { - if (oldDepFilenamePair.Key.BestVersion != dep.BestVersion && - (confirmer == null || confirmer(oldDepFilenamePair.Key, dep))) - { - DeleteExistingFileOrDirectory(oldDepFilenamePair.Value, - includeMetaFiles: true); - } - else - { - continue; - } - } - - string aarFile = dep.BestVersionArtifact; - - if (aarFile != null) - { - string baseName = Path.GetFileNameWithoutExtension(aarFile); - string extension = Path.GetExtension(aarFile); - string destName = Path.Combine(destDirectory, baseName) + - (extension == ".srcaar" ? ".aar" : extension); - string destNameUnpacked = Path.Combine( - destDirectory, Path.GetFileNameWithoutExtension(destName)); - string existingName = - File.Exists(destName) ? destName : - Directory.Exists(destNameUnpacked) ? destNameUnpacked : null; - - bool doCopy = true; - if (existingName != null) - { - doCopy = File.GetLastWriteTime(existingName).CompareTo( - File.GetLastWriteTime(aarFile)) < 0; - if (doCopy) - { - DeleteExistingFileOrDirectory(existingName, - includeMetaFiles: true); - } - } - if (doCopy) - { - File.Copy(aarFile, destName); - } - } - else - { - throw new ResolutionException("Cannot find artifact for " + dep); - } - } + clientDependenciesMap = new Dictionary(); + AdditionalRepositoryPaths = new List>(); } /// - /// Create a unique sorted list of items from a list with duplicate items. + /// Create a unique ordered list of items from a list with duplicate items. + /// Only the first occurrence of items in the original list are kept. /// private static List UniqueList(List list) { + // We can't just copy hash to list since we are preserving order. HashSet hashSet = new HashSet(); - List outputList = new List(list); + List outputList = new List(); foreach (var item in list) { if (hashSet.Contains(item)) continue; @@ -783,202 +348,6 @@ private static List UniqueList(List list) return outputList; } - /// - /// Reads the maven metadata for an artifact. - /// This reads the list of available versions. - /// - /// Dependency to process - /// file name of the metadata. - internal static void ProcessMetadata(Dependency dep, string fname) - { - XmlTextReader reader = new XmlTextReader(new StreamReader(fname)); - - bool inVersions = false; - while (reader.Read()) - { - if (reader.Name == "versions") - { - inVersions = reader.IsStartElement(); - } - else if (inVersions && reader.Name == "version") - { - dep.AddVersion(reader.ReadString()); - } - } - } - - internal Dependency FindCandidate(Dependency dep) - { - return FindCandidate(dep, repositoryPaths); - } - - internal static Dependency FindCandidate(Dependency dep, List repoPaths) - { - // If artifacts associated with dependencies have been found, return this dependency.. - if (!String.IsNullOrEmpty(dep.RepoPath) && dep.HasPossibleVersions) return dep; - - // Build a set of repositories to search for the dependency. - List searchPaths = new List(); - if (!String.IsNullOrEmpty(dep.RepoPath)) searchPaths.Add(dep.RepoPath); - searchPaths.AddRange(dep.Repositories ?? new string[] {}); - if (repoPaths != null) searchPaths.AddRange(repoPaths); - - // Search for the dependency. - foreach(string repo in UniqueList(searchPaths)) - { - string repoPath; - if (repo.StartsWith(SdkVariable)) - { - if (String.IsNullOrEmpty(SDKInternal)) - { - throw new ResolutionException(AndroidSdkConfigurationError); - } - repoPath = repo.Replace(SdkVariable, SDKInternal); - } - else - { - repoPath = repo; - } - if (Directory.Exists(repoPath)) - { - Dependency d = FindCandidate(repoPath, dep); - if (d != null) - { - return d; - } - } - else - { - Log("Repo not found: " + Path.GetFullPath(repoPath)); - } - } - Log(String.Format("ERROR: Unable to find dependency {0} in paths ({1}).\n\n" + - "{0} was referenced by:\n{2}\n\n", - dep.Key, String.Join(", ", new List(repoPaths).ToArray()), - dep.CreatedBy)); - return null; - } - - /// - /// Finds an acceptable candidate for the given dependency. - /// - /// The dependency modified so BestVersion returns the best version. - /// The path to the artifact repository. - /// The dependency to find a specific version for. - internal static Dependency FindCandidate(string repoPath, Dependency dep) - { - string basePath = Path.Combine(dep.Group, dep.Artifact); - basePath = basePath.Replace(".", Path.DirectorySeparatorChar.ToString()); - - string metadataFile = Path.Combine(Path.Combine(repoPath, basePath), - "maven-metadata.xml"); - if (File.Exists(metadataFile)) - { - ProcessMetadata(dep, metadataFile); - dep.RepoPath = repoPath; - while (dep.HasPossibleVersions) - { - // Check for the actual file existing, otherwise skip this version. - string aarFile = dep.BestVersionArtifact; - if (aarFile != null) return dep; - Log(dep.Key + " version " + dep.BestVersion + " not available, ignoring.", - verbose: true); - dep.RemovePossibleVersion(dep.BestVersion); - } - } - return null; - } - - internal IEnumerable GetDependencies(Dependency dep) - { - return GetDependencies(dep, repositoryPaths); - } - - /// - /// Gets the dependencies of the given dependency. - /// This is done by reading the .pom file for the BestVersion - /// of the dependency. - /// - /// The dependencies. - /// Dependency to process - /// Set of additional repo paths to search for the - /// dependencies. - internal static IEnumerable GetDependencies(Dependency dep, - List repoPaths) - { - List dependencyList = new List(); - if (String.IsNullOrEmpty(dep.BestVersion)) - { - Log(String.Format("ERROR: No compatible versions of {0} found given the set of " + - "required dependencies.\n\n{0} was referenced by:\n{1}\n\n", - dep.Key, dep.CreatedBy)); - return dependencyList; - } - - string basename = dep.Artifact + "-" + dep.BestVersion + ".pom"; - string pomFile = Path.Combine(dep.BestVersionPath, basename); - Log("GetDependencies - reading pom of " + basename + " pom: " + pomFile + " " + - " versions: " + - String.Join(", ", (new List(dep.PossibleVersions)).ToArray()), - verbose: true); - - XmlTextReader reader = new XmlTextReader(new StreamReader(pomFile)); - bool inDependencies = false; - bool inDep = false; - string groupId = null; - string artifactId = null; - string version = null; - while (reader.Read()) - { - if (reader.Name == "dependencies") - { - inDependencies = reader.IsStartElement(); - } - - if (inDependencies && reader.Name == "dependency") - { - inDep = reader.IsStartElement(); - } - - if (inDep && reader.Name == "groupId") - { - groupId = reader.ReadString(); - } - - if (inDep && reader.Name == "artifactId") - { - artifactId = reader.ReadString(); - } - - if (inDep && reader.Name == "version") - { - version = reader.ReadString(); - } - - // if we ended the dependency, add it - if (!string.IsNullOrEmpty(artifactId) && !inDep) - { - // Unfortunately, the Maven POM doesn't contain metadata to map the package - // to each Android SDK package ID so the list "packageIds" is left as null in - // this case. - Dependency d = FindCandidate(new Dependency(groupId, artifactId, version), - repoPaths); - if (d == null) - { - throw new ResolutionException("Cannot find candidate artifact for " + - groupId + ":" + artifactId + ":" + version); - } - - groupId = null; - artifactId = null; - version = null; - dependencyList.Add(d); - } - } - - return dependencyList; - } - /// /// Resets the dependencies. FOR TESTING ONLY!!! /// @@ -986,51 +355,5 @@ internal static void ResetDependencies() { if (instances != null) instances.Clear(); } - - /// - /// Loads the dependencies from the current PlayServicesSupport instances. - /// - /// If true, all client dependencies are loaded and returned - /// - /// If false, missing dependencies result in a - /// ResolutionException being thrown. If true, each missing dependency is included in - /// the returned set with RepoPath set to an empty string. - /// Search repositories for each candidate dependency. - /// Dictionary of dependencies - public Dictionary LoadDependencies( - bool allClients, bool keepMissing = false, bool findCandidates = true) - { - Dictionary dependencyMap = - new Dictionary(); - // Aggregate dependencies of the specified set of PlayServicesSupport instances. - PlayServicesSupport[] playServicesSupportInstances; - playServicesSupportInstances = allClients ? - (new List(instances.Values)).ToArray() : new [] { this };; - foreach (var instance in playServicesSupportInstances) - { - var newMap = new Dictionary(); - foreach (var dependencyItem in instance.clientDependenciesMap) - { - Dependency foundDependency = null; - if (findCandidates) - { - foundDependency = instance.FindCandidate(dependencyItem.Value); - } - if (foundDependency == null) - { - if (!keepMissing) - { - throw new ResolutionException("Cannot find candidate artifact for " + - dependencyItem.Value.Key); - } - foundDependency = dependencyItem.Value; - } - newMap[foundDependency.Key] = foundDependency; - dependencyMap[foundDependency.Key] = foundDependency; - } - instance.clientDependenciesMap = newMap; - } - return dependencyMap; - } } } 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/JarResolverTests/src/Google.JarResolver.Tests/DependencyTests.cs b/source/JarResolverTests/src/Google.JarResolver.Tests/DependencyTests.cs deleted file mode 100644 index 5e034c52..00000000 --- a/source/JarResolverTests/src/Google.JarResolver.Tests/DependencyTests.cs +++ /dev/null @@ -1,295 +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.JarResolvers.Test -{ - using Google.JarResolver; - using NUnit.Framework; - - /// - /// Dependency tests. - /// - [TestFixture] - public class DependencyTests - { - /// - /// Tests the constructor. - /// - [Test] - public void TestConstructor() - { - Dependency dep = new Dependency("test", "artifact1", "1.0"); - Assert.NotNull(dep); - } - - /// - /// Tests an unitialized dependency. - /// - [Test] - public void TestUninitialized() - { - Dependency dep = new Dependency("test", "artifact1", "1.0"); - - // No metadata loaded - so best version should be empty. - string ver = dep.BestVersion; - Assert.IsNullOrEmpty(ver); - Assert.IsNullOrEmpty(dep.BestVersionPath); - Assert.IsNullOrEmpty(dep.RepoPath); - - // the key is based on the spec, so it should be set. - string key = dep.Key; - Assert.IsNotNullOrEmpty(key); - - // package need to be the prefix - Assert.True(key.StartsWith("test")); - - // Versionless key should be set. and be the prefix of the key - Assert.IsNotNullOrEmpty(dep.VersionlessKey); - Assert.True(key.StartsWith(dep.VersionlessKey)); - - Assert.False(dep.HasPossibleVersions); - } - - /// - /// Tests the is newer method. - /// - [Test] - public void TestIsNewer() - { - Dependency dep09 = new Dependency("test", "artifact1", "0.9"); - Dependency dep = new Dependency("test", "artifact1", "1.0"); - Dependency dep2 = new Dependency("test", "artifact1", "2.0"); - Dependency dep11 = new Dependency("test", "artifact1", "1.1"); - Dependency dep101 = new Dependency("test", "artifact1", "1.0.1"); - Dependency dep31 = new Dependency("test", "artifact1", "3.1"); - Dependency dep32alpha = new Dependency("test", "artifact1", "3.2-alpha"); - Dependency dep32beta = new Dependency("test", "artifact1", "3.2-beta"); - Assert.False(dep09.IsNewer(dep)); - Assert.False(dep.IsNewer(dep)); - Assert.True(dep2.IsNewer(dep)); - Assert.True(dep11.IsNewer(dep)); - Assert.True(dep101.IsNewer(dep)); - Assert.True(dep31.IsNewer(dep)); - Assert.True(dep32alpha.IsNewer(dep31)); - Assert.True(dep32alpha.IsNewer(dep)); - Assert.True(dep32beta.IsNewer(dep32alpha)); - } - - /// - /// Tests the is acceptable version method - /// - [Test] - public void TestIsAcceptableVersion() - { - // concrete version. only one should be acceptable. - Dependency dep = new Dependency("test", "artifact1", "1.0"); - - Assert.True(dep.IsAcceptableVersion("1.0")); - - // trailing 0 is acceptable. - Assert.True(dep.IsAcceptableVersion("1.0.0")); - - // 2 trailing 0s is ok too. - Assert.True(dep.IsAcceptableVersion("1")); - - // greater major, or minor is not acceptable. - Assert.False(dep.IsAcceptableVersion("2.0")); - Assert.False(dep.IsAcceptableVersion("1.1")); - Assert.False(dep.IsAcceptableVersion("1.0.1")); - - // Check the LATEST meta-version - Dependency latest = new Dependency("test", "artifact1", "LATEST"); - - // Any version is acceptable until one has been added - Assert.True(latest.IsAcceptableVersion("0.1")); - Assert.True(latest.IsAcceptableVersion("1.0")); - Assert.True(latest.IsAcceptableVersion("1.1")); - - // Check the + on the minor - Dependency greaterminor = new Dependency("test", "artifact1", "1.0+"); - Dependency greaterDot = new Dependency("test", "artifact1", "1.+"); - Assert.True(greaterminor.IsAcceptableVersion("1.0")); - Assert.True(greaterminor.IsAcceptableVersion("1.0.0")); - Assert.True(greaterminor.IsAcceptableVersion("1.0.1")); - Assert.True(greaterminor.IsAcceptableVersion("1.1")); - Assert.True(greaterminor.IsAcceptableVersion("1.1.1")); - Assert.True(greaterminor.IsAcceptableVersion("1.12")); - Assert.False(greaterminor.IsAcceptableVersion("0.1")); - Assert.False(greaterminor.IsAcceptableVersion("0.0.4")); - Assert.False(greaterminor.IsAcceptableVersion("LATEST")); - Assert.False(greaterminor.IsAcceptableVersion("2.0")); - Assert.False(greaterminor.IsAcceptableVersion("2.1")); - - Assert.True(greaterDot.IsAcceptableVersion("1.0")); - Assert.True(greaterDot.IsAcceptableVersion("1.0.0")); - Assert.True(greaterDot.IsAcceptableVersion("1.0.1")); - Assert.True(greaterDot.IsAcceptableVersion("1.1")); - Assert.True(greaterDot.IsAcceptableVersion("1.1.1")); - Assert.True(greaterDot.IsAcceptableVersion("1.12")); - Assert.False(greaterDot.IsAcceptableVersion("2.0")); - Assert.False(greaterDot.IsAcceptableVersion("2.1")); - - // Check the + on the minor - Dependency majorGreater = new Dependency("test", "artifact1", "1+"); - Assert.True(majorGreater.IsAcceptableVersion("1.0")); - Assert.True(majorGreater.IsAcceptableVersion("1.0.0")); - Assert.True(majorGreater.IsAcceptableVersion("1.0.1")); - Assert.True(majorGreater.IsAcceptableVersion("1.1")); - Assert.True(majorGreater.IsAcceptableVersion("1.1.1")); - Assert.True(majorGreater.IsAcceptableVersion("2.0")); - Assert.True(majorGreater.IsAcceptableVersion("2.1.0")); - } - - /// - /// Tests the possible versions management. - /// - [Test] - public void TestPossibleVersions() - { - Dependency dep = new Dependency("test", "artifact1", "2.0"); - - // adding multiple versions to a concrete version results in only that one. - dep.AddVersion("0.1"); - - Assert.False(dep.HasPossibleVersions); - - dep.AddVersion("1.0"); - dep.AddVersion("2.0.0"); - dep.AddVersion("3.0"); - - Assert.True(dep.HasPossibleVersions); - - Assert.True(dep.BestVersion == "2.0.0"); - - dep.RemovePossibleVersion(dep.BestVersion); - - Assert.False(dep.HasPossibleVersions); - - dep = new Dependency("test", "artifact1", "2.0+"); - - // check plus - dep.AddVersion("1.0"); - dep.AddVersion("2.0.0"); - dep.AddVersion("2.0.1"); - dep.AddVersion("2.1"); - dep.AddVersion("3.0"); - - Assert.True(dep.HasPossibleVersions); - Assert.True(dep.BestVersion == "2.1"); - dep.RemovePossibleVersion(dep.BestVersion); - Assert.True(dep.BestVersion == "2.0.1"); - dep.RemovePossibleVersion(dep.BestVersion); - Assert.True(dep.BestVersion == "2.0.0"); - dep.RemovePossibleVersion(dep.BestVersion); - Assert.IsNullOrEmpty(dep.BestVersion); - Assert.False(dep.HasPossibleVersions); - - dep = new Dependency("test", "artifact1", "2.0+"); - - // check plus - dep.AddVersion("3.0"); - dep.AddVersion("2.2.0"); - dep.AddVersion("2.0.1"); - dep.AddVersion("2.1"); - dep.AddVersion("1.0"); - - Assert.True(dep.HasPossibleVersions); - Assert.True(dep.BestVersion == "2.2.0"); - dep.RemovePossibleVersion(dep.BestVersion); - Assert.True(dep.BestVersion == "2.1"); - dep.RemovePossibleVersion(dep.BestVersion); - Assert.True(dep.BestVersion == "2.0.1"); - dep.RemovePossibleVersion(dep.BestVersion); - Assert.IsNullOrEmpty(dep.BestVersion); - Assert.False(dep.HasPossibleVersions); - } - - /// - /// Tests the refine version range method. - /// - [Test] - public void TestRefineVersionRange() - { - Dependency dep = new Dependency("test", "artifact1", "2.0+"); - - dep.AddVersion("3.0"); - dep.AddVersion("2.2.0"); - dep.AddVersion("2.0.1"); - dep.AddVersion("2.1"); - dep.AddVersion("1.0"); - - // refinement with the same object should have no effect. - Assert.True(dep.RefineVersionRange(dep)); - Assert.True(dep.HasPossibleVersions); - Assert.True(dep.BestVersion == "2.2.0"); - dep.RemovePossibleVersion(dep.BestVersion); - Assert.True(dep.BestVersion == "2.1"); - dep.RemovePossibleVersion(dep.BestVersion); - Assert.True(dep.BestVersion == "2.0.1"); - dep.RemovePossibleVersion(dep.BestVersion); - Assert.IsNullOrEmpty(dep.BestVersion); - Assert.False(dep.HasPossibleVersions); - - // refinement with a concrete version not compatible should fail. - dep = new Dependency("test", "artifact1", "2.0+"); - - dep.AddVersion("3.0"); - dep.AddVersion("2.2.0"); - dep.AddVersion("2.0.1"); - dep.AddVersion("2.1"); - dep.AddVersion("1.0"); - - Dependency dep1 = new Dependency("test", "artifact1", "3.0"); - - Assert.False(dep.RefineVersionRange(dep1)); - Assert.False(dep.HasPossibleVersions); - - // concrete included - dep = new Dependency("test", "artifact1", "2.0+"); - - dep.AddVersion("3.0"); - dep.AddVersion("2.2.0"); - dep.AddVersion("2.0.1"); - dep.AddVersion("2.1"); - dep.AddVersion("1.0"); - - Dependency dep2 = new Dependency("test", "artifact1", "2.1.0"); - - Assert.True(dep.RefineVersionRange(dep2)); - Assert.True(dep.BestVersion == "2.1"); - dep.RemovePossibleVersion(dep.BestVersion); - Assert.False(dep.HasPossibleVersions); - - // check overlapping ranges - dep = new Dependency("test", "artifact1", "2.0+"); - - dep.AddVersion("3.0"); - dep.AddVersion("2.2.0"); - dep.AddVersion("2.0.1"); - dep.AddVersion("2.1"); - dep.AddVersion("1.0"); - - Dependency dep1plus = new Dependency("test", "artifact1", "2.1+"); - - Assert.True(dep.RefineVersionRange(dep1plus)); - Assert.True(dep.BestVersion == "2.2.0"); - dep.RemovePossibleVersion(dep.BestVersion); - Assert.True(dep.BestVersion == "2.1"); - dep.RemovePossibleVersion(dep.BestVersion); - Assert.False(dep.HasPossibleVersions); - } - } -} \ No newline at end of file diff --git a/source/JarResolverTests/src/Google.JarResolver.Tests/PlayServicesSupportTests.cs b/source/JarResolverTests/src/Google.JarResolver.Tests/PlayServicesSupportTests.cs deleted file mode 100644 index 2f1194b8..00000000 --- a/source/JarResolverTests/src/Google.JarResolver.Tests/PlayServicesSupportTests.cs +++ /dev/null @@ -1,496 +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.Editor.Tests -{ - using System; - using System.Collections.Generic; - using System.IO; - using Google.JarResolver; - using NUnit.Framework; - - /// - /// Test data and methods to create it. - /// - internal static class TestData { - // Whether to display verbose log messages. - public const bool VERBOSE_LOGGING = false; - - // Path to test data, contains a mock SDK and maven repo. - public const string PATH = "../../testData"; - - // Stores expected data for Maven artifacts in TestData.PATH. - public class PackageInfo { - public string group; - public string artifact; - public string bestVersion; - }; - - // Maven artifacts available in TestData.PATH. - public enum PackageId { - Artifact, - TransDep, - SubDep, - }; - - // Properties of Maven artifacts in TestData.PATH. - public static Dictionary mavenArtifacts = - new Dictionary { - { - PackageId.Artifact, - new PackageInfo { - group = "test", - artifact = "artifact", - bestVersion = "8.2.0-alpha" - } - }, - { - PackageId.TransDep, - new PackageInfo { - group = "test", - artifact = "transdep", - bestVersion = "1.0.0", - } - }, - { - PackageId.SubDep, - new PackageInfo { - group = "test", - artifact = "subdep", - bestVersion = "0.9", - } - } - }; - - /// - /// Extension method of PackageId that returns the associated - /// PackageInfo instance. - /// - internal static PackageInfo Info(this PackageId artifactId) - { - return mavenArtifacts[artifactId]; - } - - /// - /// Extension method of PackageId that returns a version less key for the artifact - /// in the form "group:artifact". - /// - internal static string VersionlessKey(this PackageId artifactId) - { - var info = artifactId.Info(); - return String.Format("{0}:{1}", info.group, info.artifact); - } - - /// - /// Create a PlayServicesSupport instance for testing. - /// - public static PlayServicesSupport CreateInstance( - string instanceName = null, string sdkPath = null, - string[] additionalRepositories = null, PlayServicesSupport.LogMessage logger = null) - { - var instance = PlayServicesSupport.CreateInstance( - instanceName ?? "testInstance", sdkPath ?? PATH, - additionalRepositories, Path.GetTempPath(), logger: logger ?? Console.WriteLine); - PlayServicesSupport.verboseLogging = VERBOSE_LOGGING; - return instance; - } - - /// - /// Extension method for PlayServicesSupport that calls DependOn with the specified - /// artifact ID. - /// - internal static void DependOn(this PlayServicesSupport instance, PackageId artifactId, - string versionSpecifier) - { - var info = artifactId.Info(); - instance.DependOn(info.group, info.artifact, versionSpecifier); - } - } - - /// - /// Play services support tests. - /// - [TestFixture] - public class PlayServicesSupportTests - { - /// - /// Clear all PlayServicesSupport instances. - /// - [SetUp] - public void SetUp() - { - PlayServicesSupport.ResetDependencies(); - } - - /// - /// Simple, "happy path" tests. - /// - [Test] - public void TestSimpleResolveDependencies() - { - PlayServicesSupport support = TestData.CreateInstance(); - - Assert.True(Directory.Exists(support.SDK)); - - support.DependOn(TestData.PackageId.Artifact, "LATEST"); - - Dictionary deps = support.ResolveDependencies(false); - Assert.NotNull(deps); - - // Verify one single dependency is returned at the expected version. - Assert.AreEqual(1, deps.Count); - IEnumerator iter = deps.Values.GetEnumerator(); - iter.MoveNext(); - Assert.AreEqual(TestData.PackageId.Artifact.Info().bestVersion, - iter.Current.BestVersion); - } - - /// - /// Tests adding another repo path. This is simulated by giving - /// the incorrect SDK path, and adding the correct repo path as - /// an additional one. - /// - [Test] - public void TestCustomRepoPath() - { - string[] repos = {Path.Combine(TestData.PATH, "extras/google/m2repository")}; - PlayServicesSupport support = TestData.CreateInstance( - sdkPath: "..", additionalRepositories: repos); - - Assert.True(Directory.Exists(support.SDK)); - - support.ClearDependencies(); - support.DependOn(TestData.PackageId.Artifact, "LATEST"); - - Dictionary deps = support.ResolveDependencies(false); - Assert.NotNull(deps); - - // Verify one dependency is returned at the expected version. - Assert.AreEqual(1, deps.Count); - IEnumerator iter = deps.Values.GetEnumerator(); - iter.MoveNext(); - Assert.AreEqual(TestData.PackageId.Artifact.Info().bestVersion, - iter.Current.BestVersion); - } - - /// - /// Tests resolving transitive dependencies. - /// - [Test] - public void TestResolveDependencies() - { - PlayServicesSupport support = TestData.CreateInstance(); - - Assert.True(Directory.Exists(support.SDK)); - - support.DependOn(TestData.PackageId.Artifact, "LATEST"); - - Dictionary deps = - support.ResolveDependencies(false); - Assert.NotNull(deps); - - // Verify one dependency is returned at the expected version. - Assert.AreEqual(1, deps.Count); - IEnumerator iter = deps.Values.GetEnumerator(); - iter.MoveNext(); - Assert.AreEqual(TestData.PackageId.Artifact.Info().bestVersion, - iter.Current.BestVersion); - - // Check dependency with has transitive dependencies. - support.DependOn(TestData.PackageId.TransDep, "1.0"); - - deps = support.ResolveDependencies(false); - Assert.NotNull(deps); - - // One dependency should be present from the previous test and an additional two - // for the transdep and subdep. - Assert.AreEqual(3, deps.Count); - Dependency d = deps[TestData.PackageId.Artifact.VersionlessKey()]; - Assert.AreEqual(TestData.PackageId.Artifact.Info().bestVersion, d.BestVersion); - d = deps[TestData.PackageId.TransDep.VersionlessKey()]; - Assert.AreEqual(TestData.PackageId.TransDep.Info().bestVersion, d.BestVersion); - d = deps[TestData.PackageId.SubDep.VersionlessKey()]; - Assert.AreEqual(TestData.PackageId.SubDep.Info().bestVersion, d.BestVersion); - - // check constraining down to a later version - the LATEST - // will make this fail. - support.DependOn(TestData.PackageId.Artifact, "7.0.0"); - - ResolutionException ex = null; - try - { - deps = support.ResolveDependencies(false); - } - catch (ResolutionException e) - { - ex = e; - } - - Assert.NotNull(ex); - - // Now add it as 7+ and LATEST and it will work. - support.ClearDependencies(); - - support.DependOn(TestData.PackageId.Artifact, "LATEST"); - support.DependOn(TestData.PackageId.Artifact, "7+"); - deps = support.ResolveDependencies(false); - Assert.NotNull(deps); - d = deps[TestData.PackageId.Artifact.VersionlessKey()]; - Assert.AreEqual(TestData.PackageId.Artifact.Info().bestVersion, d.BestVersion); - - // Test downversioning. - support.ClearDependencies(); - - support.DependOn(TestData.PackageId.Artifact, "1+"); - support.DependOn(TestData.PackageId.Artifact, "2+"); - support.DependOn(TestData.PackageId.Artifact, "7.0.0"); - - deps = support.ResolveDependencies(false); - Assert.NotNull(deps); - d = deps[TestData.PackageId.Artifact.VersionlessKey()]; - Assert.AreEqual("7.0.0", d.BestVersion); - - // test the transitive dep influencing a top level - support.ClearDependencies(); - - support.DependOn(TestData.PackageId.Artifact, "1+"); - support.DependOn(TestData.PackageId.SubDep, "0+"); - support.DependOn(TestData.PackageId.TransDep, "LATEST"); - - deps = support.ResolveDependencies(false); - Assert.NotNull(deps); - d = deps[TestData.PackageId.Artifact.VersionlessKey()]; - Assert.AreEqual(TestData.PackageId.Artifact.Info().bestVersion, d.BestVersion); - d = deps[TestData.PackageId.SubDep.VersionlessKey()]; - Assert.AreEqual(TestData.PackageId.SubDep.Info().bestVersion, d.BestVersion); - } - - /// - /// Tests the use latest version contraint. - /// - [Test] - public void TestUseLatest() - { - PlayServicesSupport support = TestData.CreateInstance(); - - Assert.True(Directory.Exists(support.SDK)); - - support.DependOn(TestData.PackageId.Artifact, "1+"); - support.DependOn(TestData.PackageId.SubDep, "1.1.0"); - support.DependOn(TestData.PackageId.TransDep, "LATEST"); - - Dictionary deps = - support.ResolveDependencies(true); - Assert.NotNull(deps); - Dependency d = deps[TestData.PackageId.Artifact.VersionlessKey()]; - Assert.AreEqual(TestData.PackageId.Artifact.Info().bestVersion, d.BestVersion); - d = deps[TestData.PackageId.SubDep.VersionlessKey()]; - Assert.AreEqual("1.1.0", d.BestVersion); - } - - /// - /// Tests the multi client scenario where 2 clients have different dependencies. - /// - [Test] - public void TestMultiClient() - { - PlayServicesSupport client1 = TestData.CreateInstance(instanceName: "client1"); - PlayServicesSupport client2 = TestData.CreateInstance(instanceName: "client2"); - - client1.DependOn(TestData.PackageId.Artifact, "1+"); - client2.DependOn(TestData.PackageId.SubDep, "1.1.0"); - - Dictionary deps = - client1.ResolveDependencies(true); - Assert.NotNull(deps); - Dependency d = deps[TestData.PackageId.Artifact.VersionlessKey()]; - Assert.AreEqual(TestData.PackageId.Artifact.Info().bestVersion, d.BestVersion); - - // client 1 needs to see client 2 deps - d = deps[TestData.PackageId.SubDep.VersionlessKey()]; - Assert.AreEqual("1.1.0", d.BestVersion); - - // now check that client 2 sees them also - deps = - client2.ResolveDependencies(true); - Assert.NotNull(deps); - d = deps[TestData.PackageId.Artifact.VersionlessKey()]; - Assert.AreEqual(TestData.PackageId.Artifact.Info().bestVersion, d.BestVersion); - - d = deps[TestData.PackageId.SubDep.VersionlessKey()]; - Assert.AreEqual("1.1.0", d.BestVersion); - - // Now clear client2's deps, and client1 should not see subdep - client2.ClearDependencies(); - - deps = client1.ResolveDependencies(true); - Assert.NotNull(deps); - d = deps[TestData.PackageId.Artifact.VersionlessKey()]; - Assert.AreEqual(TestData.PackageId.Artifact.Info().bestVersion, d.BestVersion); - - Assert.False(deps.ContainsKey(TestData.PackageId.SubDep.VersionlessKey())); - } - - /// - /// Tests the latest resolution strategy when one dependency has - /// a transitive dependency that is old, and the caller is requiring - /// a newer version, then the dependencies are resolved with the - /// useLatest flag == true. - /// - [Test] - public void TestLatestResolution() - { - PlayServicesSupport client1 = TestData.CreateInstance(); - - // TransDep needs SubDep 0.9. - client1.DependOn(TestData.PackageId.TransDep, "1.0.0"); - - // We'll set the top level dependency to require SubDep 1.0 or greater. - client1.DependOn(TestData.PackageId.SubDep, "1.0+"); - - Dictionary deps = null; - - // The following should fail since we need SubDep 0.9 and SubDep 1.1.0. - ResolutionException ex = null; - try - { - deps = client1.ResolveDependencies(false); - } - catch (ResolutionException e) - { - ex = e; - } - - Assert.NotNull(ex, "Expected exception, but got none"); - - // now try with useLatest == true, should have no exception - ex = null; - try - { - deps = client1.ResolveDependencies(true); - } - catch (ResolutionException e) - { - ex = e; - } - Assert.Null(ex, "unexpected exception"); - - Assert.NotNull(deps); - - // Should have TransDep and SubDep. - Assert.AreEqual(2, deps.Count, - String.Join(", ", new List(deps.Keys).ToArray())); - - // Now check that that all the dependencies have the correct best version. - Dependency d = deps[TestData.PackageId.TransDep.VersionlessKey()]; - Assert.NotNull(d, "could not find transdep"); - Assert.AreEqual(TestData.PackageId.TransDep.Info().bestVersion, d.BestVersion); - - d = deps[TestData.PackageId.SubDep.VersionlessKey()]; - Assert.NotNull(d, "could not find subdep"); - Assert.AreEqual("1.1.0", d.BestVersion); - - // Try without version wildcard. - client1.ClearDependencies(); - - // TransDep needs subdep 0.9. - client1.DependOn(TestData.PackageId.TransDep, "1.0.0"); - - // Configure top level dependency to require exactly subdep 1.1.0. - client1.DependOn(TestData.PackageId.SubDep, "1.1.0"); - - ex = null; - try - { - deps = client1.ResolveDependencies(false); - } - catch (ResolutionException e) - { - ex = e; - } - - Assert.NotNull(ex, "Expected exception, but got none"); - - ex = null; - try - { - deps = client1.ResolveDependencies(true); - } - catch (ResolutionException e) - { - ex = e; - } - Assert.Null(ex, "unexpected exception"); - - Assert.NotNull(deps); - - // Should contain TransDep and SubDep. - Assert.AreEqual(2, deps.Count); - - // now check that that all the dependencies have the correct - // best version - d = deps[TestData.PackageId.TransDep.VersionlessKey()]; - Assert.NotNull(d, "could not find transdep"); - Assert.AreEqual(TestData.PackageId.TransDep.Info().bestVersion, d.BestVersion); - - d = deps[TestData.PackageId.SubDep.VersionlessKey()]; - Assert.NotNull(d, "could not find subdep"); - Assert.AreEqual("1.1.0", d.BestVersion); - } - - /// - /// Tests the non active client scenario where the current client has - /// no dependencies, but it resolves all the clients in the project. - /// - [Test] - public void TestNonActiveClient() - { - PlayServicesSupport client1 = TestData.CreateInstance(instanceName: "client1"); - PlayServicesSupport client2 = TestData.CreateInstance(instanceName: "client2"); - - client1.DependOn(TestData.PackageId.Artifact, "1+"); - client2.DependOn(TestData.PackageId.SubDep, "1.1.0"); - - // now make a third client with no dependencies and make sure it - // sees client1 & 2 - PlayServicesSupport client3 = TestData.CreateInstance(instanceName: "client3"); - - // now check that client 2 sees them also - Dictionary deps = client3.ResolveDependencies(true); - Assert.NotNull(deps); - Dependency d = deps[TestData.PackageId.Artifact.VersionlessKey()]; - Assert.AreEqual(TestData.PackageId.Artifact.Info().bestVersion, d.BestVersion); - - d = deps[TestData.PackageId.SubDep.VersionlessKey()]; - Assert.AreEqual("1.1.0", d.BestVersion); - } - - /// - /// Verify the logger delegate is called by the Log() method. - /// - [Test] - public void TestLogger() - { - List messageList = new List(); - string logMessage = "this is a test"; - PlayServicesSupport.logger = (message) => messageList.Add(message); - Assert.AreEqual(0, messageList.Count); - PlayServicesSupport.Log(logMessage); - Assert.AreEqual(1, messageList.Count); - Assert.AreEqual(logMessage, messageList[0]); - } - } -} diff --git a/source/PackageManager/src/Constants.cs b/source/PackageManager/src/Constants.cs deleted file mode 100644 index 33b68887..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 pacakge 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 0dc710f2..00000000 --- a/source/PackageManager/src/Controllers.cs +++ /dev/null @@ -1,1786 +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 propery 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) succeded - /// - 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); - /// - /// Thead 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 inconvenent 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"); - } - - /// - /// Refreshs 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 assiciates 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 gaurenteed 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 ditry - 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)); - } - } - - /// - /// Refreshs 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); - } - - var dependencySet = support.ResolveDependencies(true); - client.depNames.AddRange(dependencySet.Keys); - WriteProjectPackages(); - EnsurePluginsDirectory(); - - try { - LoggingController.Log( - string.Format("About to resolve for client: {0}", client.Name)); - GooglePlayServices.PlayServicesResolver.Resolver.DoResolution(support, - "Assets/Plugins/Android", - GooglePlayServices.PlayServicesResolver.HandleOverwriteConfirmation, - () => { - 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); - } - - /// - /// Refreshs 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; - } - } -} \ No newline at end of file diff --git a/source/PackageManager/src/Models.cs b/source/PackageManager/src/Models.cs deleted file mode 100644 index 39bd7218..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 basd 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}, verison = {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 epoc 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 repesentations 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 sentance. - /// - [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 fd3089d7..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 occured 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 1dd54a0c..00000000 --- a/source/PackageManagerTests/src/Google.PackageManager.Tests/ControllerTests.cs +++ /dev/null @@ -1,334 +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 const string PATH = "../../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 excercises 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 71a50b76..00000000 --- a/source/PackageManagerTests/src/Google.PackageManager.Tests/ModelTests.cs +++ /dev/null @@ -1,104 +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 { - // Path to test data, contains a mock registry and settings config. - public const string PATH = "../../testData"; - - /// - /// Tests root models are able to load from file. - /// Root models include: - /// - Repository - /// - Description - /// - PluginMetaData - /// - PackageExportSettings - /// - [Test] - public void TestLoadFromFile() { - string registryPath = Path.Combine(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(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(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); - } - } -} \ No newline at end of file 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/Constants.cs b/source/PlayServicesResolver/src/Constants.cs deleted file mode 100644 index 3c1c5781..00000000 --- a/source/PlayServicesResolver/src/Constants.cs +++ /dev/null @@ -1,28 +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.PlayServicesResolver { - /// - /// Collection of constants relating to the PlayServicesResolver set of classes. - /// - public static class Constants { - public const string ANDROID_PLUGIN_ASSET_DIRECTORY = "Assets/Plugins/Android"; - - public const string SETTINGS_KEY_AUTO_RESOLUTION = - "GooglePlayServices.AutoResolverEnabled"; - public const string SETTINGS_KEY_INSTALL_ANDROID_PACKAGES = - "GooglePlayServices.AndroidPackageInstallationEnabled"; - } -} diff --git a/source/PlayServicesResolver/src/DefaultResolver.cs b/source/PlayServicesResolver/src/DefaultResolver.cs deleted file mode 100644 index b0564d2c..00000000 --- a/source/PlayServicesResolver/src/DefaultResolver.cs +++ /dev/null @@ -1,367 +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.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 - { - #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. - /// - /// true, if resolution enabled was automaticed, false otherwise. - public virtual bool AutomaticResolutionEnabled() - { - return SettingsDialog.EnableAutoResolution; - } - - /// - /// 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. - public virtual bool ShouldAutoResolve( - string[] importedAssets, - string[] deletedAssets, - string[] movedAssets, - string[] movedFromAssetPaths) - { - if (AutomaticResolutionEnabled()) - { - // look for imported scripts - foreach (string s in importedAssets) - { - if (s.EndsWith(".cs") || s.EndsWith(".js")) - { - Debug.Log(s + " imported, resolving play-services"); - return true; - } - } - - // look for deleted android plugins - foreach (string s in deletedAssets) - { - if (s.StartsWith(SettingsDialog.AndroidPluginsDir)) - { - Debug.Log(s + " deleted, resolving play-services"); - return true; - } - } - // don't resolve if assets are moved around. - } - 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. - /// Handle overwrite confirmation. - /// Delegate called when resolution is complete. - public virtual void DoResolution( - PlayServicesSupport svcSupport, string destinationDirectory, - PlayServicesSupport.OverwriteConfirmation handleOverwriteConfirmation, - System.Action resolutionComplete) - { - DoResolution(svcSupport, destinationDirectory, handleOverwriteConfirmation); - 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. - public virtual string[] OnBundleId(string bundleId) - { - return null; - } - - #endregion - - /// - /// Compatibility method for synchronous implementations of DoResolution(). - /// - /// Svc support. - /// Destination directory. - /// Handle overwrite confirmation. - public abstract void DoResolution( - PlayServicesSupport svcSupport, string destinationDirectory, - PlayServicesSupport.OverwriteConfirmation handleOverwriteConfirmation); - - /// - /// 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; - } - } - - /// - /// Find a Java tool. - /// - /// Name of the tool to search for. - private string FindJavaTool(string javaTool) - { - string javaHome = UnityEditor.EditorPrefs.GetString("JdkPath"); - javaHome = javaHome ?? Environment.GetEnvironmentVariable("JAVA_HOME"); - string toolPath; - if (javaHome != null) - { - toolPath = Path.Combine( - javaHome, Path.Combine( - "bin", javaTool + CommandLine.GetExecutableExtension())); - if (!File.Exists(toolPath)) - { - EditorUtility.DisplayDialog("Play Services Dependencies", - "JAVA_HOME environment references a directory (" + - javaHome + ") that does not contain " + javaTool + - " which is required to process Play Services " + - "dependencies.", "OK"); - throw new Exception("JAVA_HOME references incomplete Java distribution. " + - javaTool + " not found."); - } - } else { - toolPath = CommandLine.FindExecutable(javaTool); - if (!File.Exists(toolPath)) - { - EditorUtility.DisplayDialog("Play Services Dependencies", - "Unable to find " + javaTool + " in the system " + - "path. This tool is required to process Play " + - "Services dependencies. Either set JAVA_HOME " + - "or add " + javaTool + " to the PATH variable " + - "to resolve this error.", "OK"); - throw new Exception(javaTool + " not found."); - } - } - return toolPath; - } - - /// - /// Create a temporary directory. - /// - /// If temporary directory creation fails, return null. - public static string CreateTemporaryDirectory() - { - int retry = 100; - while (retry-- > 0) - { - string temporaryDirectory = Path.Combine(Path.GetTempPath(), - Path.GetRandomFileName()); - if (File.Exists(temporaryDirectory)) - { - continue; - } - Directory.CreateDirectory(temporaryDirectory); - return temporaryDirectory; - } - return null; - } - - // store the AndroidManifest.xml in a temporary directory before processing it. - /// - /// Extract an AAR to the specified directory. - /// - /// Name of the AAR file to extract. - /// List of files to extract from the AAR. If this array - /// is empty or null all files are extracted. - /// Directory to extract the AAR file to. - /// true if successful, false otherwise. - internal virtual bool ExtractAar(string aarFile, string[] extractFilenames, - string outputDirectory) - { - try - { - string aarPath = Path.GetFullPath(aarFile); - string extractFilesArg = extractFilenames != null && extractFilenames.Length > 0 ? - " \"" + String.Join("\" \"", extractFilenames) + "\"" : ""; - CommandLine.Result result = CommandLine.Run(FindJavaTool("jar"), - "xvf " + "\"" + aarPath + "\"" + - extractFilesArg, - workingDirectory: outputDirectory); - if (result.exitCode != 0) - { - Debug.LogError("Error expanding " + aarPath + " err: " + - result.exitCode + ": " + result.stderr); - return false; - } - } - catch (Exception e) - { - Debug.Log(e); - throw e; - } - return true; - } - - /// - /// Explodes a single aar file. This is done by calling the - /// JDK "jar" command, then moving the classes.jar file. - /// - /// the parent directory of the plugin. - /// Aar file to explode. - /// The path to the exploded aar. - internal virtual string ProcessAar(string dir, string aarFile) - { - string workingDir = Path.Combine(dir, Path.GetFileNameWithoutExtension(aarFile)); - PlayServicesSupport.DeleteExistingFileOrDirectory(workingDir, includeMetaFiles: true); - Directory.CreateDirectory(workingDir); - if (!ExtractAar(aarFile, null, workingDir)) return workingDir; - - // Create the libs directory to store the classes.jar and non-Java shared libraries. - string libDir = Path.Combine(workingDir, "libs"); - Directory.CreateDirectory(libDir); - - // Move the classes.jar file to libs. - string classesFile = Path.Combine(workingDir, "classes.jar"); - if (File.Exists(classesFile)) - { - string targetClassesFile = Path.Combine(libDir, Path.GetFileName(classesFile)); - if (File.Exists(targetClassesFile)) File.Delete(targetClassesFile); - File.Move(classesFile, targetClassesFile); - } - - // 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"); - if (Directory.Exists(jniLibDir)) - { - PlayServicesSupport.CopyDirectory(jniLibDir, libDir); - PlayServicesSupport.DeleteExistingFileOrDirectory(jniLibDir, - includeMetaFiles: true); - } - - // 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" - }); - } - - // Clean up the aar file. - File.Delete(Path.GetFullPath(aarFile)); - Debug.Log(aarFile + " expanded successfully"); - return workingDir; - } - } -} - diff --git a/source/PlayServicesResolver/src/IResolver.cs b/source/PlayServicesResolver/src/IResolver.cs deleted file mode 100644 index f4928465..00000000 --- a/source/PlayServicesResolver/src/IResolver.cs +++ /dev/null @@ -1,99 +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; - - public interface IResolver - { - /// - /// Version of the resolver. - /// - /// - /// The resolver with the greatest version is used when resolving. - /// The value of the verison is calcuated using MakeVersion in DefaultResolver - /// - /// - int Version(); - - /// - /// Returns true if automatic resolution is enabled. - /// - /// 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. - 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. - /// Handle overwrite confirmation. - /// Delegate called when resolution is complete. - void DoResolution(PlayServicesSupport svcSupport, - string destinationDirectory, - PlayServicesSupport.OverwriteConfirmation handleOverwriteConfirmation, - System.Action resolutionComplete); - - /// - /// Does the resolution of the play-services aars. - /// - /// Svc support. - /// Destination directory. - /// Handle overwrite confirmation. - void DoResolution(PlayServicesSupport svcSupport, - string destinationDirectory, - PlayServicesSupport.OverwriteConfirmation handleOverwriteConfirmation); - - /// - /// 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. - string[] OnBundleId(string bundleId); - } -} - diff --git a/source/PlayServicesResolver/src/PlayServicesResolver.cs b/source/PlayServicesResolver/src/PlayServicesResolver.cs deleted file mode 100644 index 84d4d389..00000000 --- a/source/PlayServicesResolver/src/PlayServicesResolver.cs +++ /dev/null @@ -1,440 +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 Google.JarResolver; - using UnityEditor; - using UnityEngine; - - /// - /// Play services resolver. This is a background post processor - /// that copies over the Google play services .aar files that - /// plugins have declared as dependencies. If the Unity version is less than - /// 5, aar files are not supported so this class 'explodes' the aar file into - /// a plugin directory. Once the version of Unity is upgraded, the exploded - /// files are removed in favor of the .aar files. - /// - [InitializeOnLoad] - public class PlayServicesResolver : AssetPostprocessor - { - /// - /// The instance to the play services support object. - /// - private static PlayServicesSupport svcSupport; - - /// - /// The resolver to use, injected to allow for version updating. - /// - private static IResolver _resolver; - - /// - /// Flag used to prevent re-entrant auto-resolution. - /// - private static bool autoResolving = false; - - /// - /// Seconds to wait until re-resolving dependencies after the bundle ID has changed. - /// - private const int bundleUpdateDelaySeconds = 3; - - /// - /// Last time the bundle ID was checked. - /// - private static DateTime lastBundleIdPollTime = DateTime.Now; - - /// - /// Last bundle ID value. - /// - private static string lastBundleId = PlayerSettings.bundleIdentifier; - - /// - /// Last value of bundle ID since the last time OnBundleId() was called. - /// - private static string bundleId = PlayerSettings.bundleIdentifier; - - /// - /// Arguments for the bundle ID update event. - /// - public class BundleIdChangedEventArgs : EventArgs { - /// - /// Current project Bundle ID. - /// - public string BundleId { get; set; } - - /// - /// Bundle ID when this event was last fired. - /// - public string PreviousBundleId { get; set; } - } - - /// - /// Event which is fired when the bundle ID is updated. - /// - public static event EventHandler BundleIdChanged; - - /// - /// The value of GradleBuildEnabled when PollBuildSystem() was called. - /// - private static bool previousGradleBuildEnabled = false; - - /// - /// The value of ProjectExportEnabled when PollBuildSystem() was called. - /// - private static bool previousProjectExportEnabled = false; - - /// - /// Get a boolean property from UnityEditor.EditorUserBuildSettings. - /// - /// Properties are introduced over successive versions of Unity so use reflection to - /// retrieve them. - private static object GetEditorUserBuildSettingsProperty(string name, - object defaultValue) - { - var editorUserBuildSettingsType = typeof(UnityEditor.EditorUserBuildSettings); - var property = editorUserBuildSettingsType.GetProperty(name); - if (property != null) - { - var value = property.GetValue(null, null); - if (value != null) return value; - } - return defaultValue; - } - - /// - /// Whether the Gradle build system is enabled. - /// - public static bool GradleBuildEnabled - { - get - { - return GetEditorUserBuildSettingsProperty( - "androidBuildSystem", "").ToString().Equals("Gradle"); - } - } - - /// - /// Whether project export is enabled. - /// - public static bool ProjectExportEnabled - { - get - { - var value = GetEditorUserBuildSettingsProperty("exportAsGoogleAndroidProject", - null); - return value == null ? false : (bool)value; - } - } - - /// - /// Arguments for the Android build system changed event. - /// - public class AndroidBuildSystemChangedArgs : EventArgs { - /// - /// Gradle was selected as the build system when this event was fired. - /// - public bool GradleBuildEnabled { get; set; } - - /// - /// Whether Gradle was selected as the build system the last time this event was fired. - /// - public bool PreviousGradleBuildEnabled { get; set; } - - /// - /// Project export was selected when this event was fired. - /// - public bool ProjectExportEnabled { get; set; } - - /// - /// Whether project export was selected when this event was fired. - /// - public bool PreviousProjectExportEnabled { get; set; } - } - - /// - /// Event which is fired when the Android build system changes. - /// - public static event EventHandler AndroidBuildSystemChanged; - - /// - /// Initializes the class. - /// - static PlayServicesResolver() - { - if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) - { - svcSupport = PlayServicesSupport.CreateInstance( - "PlayServicesResolver", - EditorPrefs.GetString("AndroidSdkRoot"), - "ProjectSettings", - logger: UnityEngine.Debug.Log); - - EditorApplication.update -= AutoResolve; - EditorApplication.update += AutoResolve; - BundleIdChanged -= ResolveOnBundleIdChanged; - BundleIdChanged += ResolveOnBundleIdChanged; - AndroidBuildSystemChanged -= ResolveOnBuildSystemChanged; - AndroidBuildSystemChanged += ResolveOnBuildSystemChanged; - } - EditorApplication.update -= PollBundleId; - EditorApplication.update += PollBundleId; - EditorApplication.update -= PollBuildSystem; - EditorApplication.update += PollBuildSystem; - } - - /// - /// Registers the resolver. - /// - /// - /// The resolver with the greatest version number is retained - /// - /// The resolver. - /// Resolver impl. - public static IResolver RegisterResolver(IResolver resolverImpl) - { - if (resolverImpl == null) - { - return _resolver; - } - if (_resolver == null || _resolver.Version() < resolverImpl.Version()) - { - _resolver = resolverImpl; - } - return _resolver; - } - - /// - /// Gets the resolver. - /// - /// The resolver. - public static IResolver Resolver - { - get - { - return _resolver; - } - } - - /// - /// Called by Unity when all assets have been updated. This - /// is used to kick off resolving the dependendencies declared. - /// - /// Imported assets. (unused) - /// Deleted assets. (unused) - /// Moved assets. (unused) - /// Moved from asset paths. (unused) - static void OnPostprocessAllAssets(string[] importedAssets, - string[] deletedAssets, - string[] movedAssets, - string[] movedFromAssetPaths) - { - if (Resolver == null) return; - - if (Resolver.ShouldAutoResolve(importedAssets, deletedAssets, - movedAssets, movedFromAssetPaths)) - { - AutoResolve(); - } - } - - /// - /// Resolve dependencies if auto-resolution is enabled. - /// - private static void AutoResolve() - { - if (Resolver.AutomaticResolutionEnabled() && !autoResolving) - { - EditorApplication.update -= AutoResolve; - // Prevent resolution on the call to OnPostprocessAllAssets(). - autoResolving = true; - Resolve(); - autoResolving = false; - } - } - - /// - /// If the user changes the bundle ID, perform resolution again. - /// - private static void ResolveOnBundleIdChanged(object sender, BundleIdChangedEventArgs args) - { - if (Resolver.AutomaticResolutionEnabled()) - { - if (DeleteFiles(Resolver.OnBundleId(args.BundleId))) AutoResolve(); - } - } - - /// - /// If the user changes the bundle ID, perform resolution again. - /// - private static void PollBundleId() - { - string currentBundleId = PlayerSettings.bundleIdentifier; - DateTime currentPollTime = DateTime.Now; - if (currentBundleId != bundleId) - { - // If the bundle ID setting hasn't changed for a while. - if (currentBundleId == lastBundleId) - { - if (currentPollTime.Subtract(lastBundleIdPollTime).Seconds >= - bundleUpdateDelaySeconds) - { - if (BundleIdChanged != null) { - BundleIdChanged(null, - new BundleIdChangedEventArgs { - PreviousBundleId = bundleId, - BundleId = currentBundleId - }); - } - bundleId = currentBundleId; - } - } - else - { - lastBundleId = currentBundleId; - lastBundleIdPollTime = currentPollTime; - } - } - } - - /// - /// If the user changes the Android build system, perform resolution again. - /// - private static void ResolveOnBuildSystemChanged(object sender, - AndroidBuildSystemChangedArgs args) - { - AutoResolve(); - } - - /// - /// Poll the Android build system selection for changes. - /// - private static void PollBuildSystem() - { - - bool gradleBuildEnabled = GradleBuildEnabled; - bool projectExportEnabled = ProjectExportEnabled; - if (previousGradleBuildEnabled != gradleBuildEnabled || - previousProjectExportEnabled != projectExportEnabled) - { - if (AndroidBuildSystemChanged != null) - { - AndroidBuildSystemChanged(null, new AndroidBuildSystemChangedArgs { - GradleBuildEnabled = gradleBuildEnabled, - PreviousGradleBuildEnabled = previousGradleBuildEnabled, - ProjectExportEnabled = projectExportEnabled, - PreviousProjectExportEnabled = previousProjectExportEnabled, - }); - } - previousGradleBuildEnabled = gradleBuildEnabled; - previousProjectExportEnabled = projectExportEnabled; - } - } - - /// - /// Delete the specified array of files and directories. - /// - /// Array of files or directories to delete. - /// true if files are deleted, false otherwise. - private static bool DeleteFiles(string[] filenames) - { - if (filenames == null) return false; - foreach (string artifact in filenames) - { - PlayServicesSupport.DeleteExistingFileOrDirectory(artifact, - includeMetaFiles: true); - } - AssetDatabase.Refresh(); - return true; - } - - /// - /// Resolve dependencies. - /// - /// Delegate called when resolution is complete. - private static void Resolve(System.Action resolutionComplete = null) - { - DeleteFiles(Resolver.OnBundleId(PlayerSettings.bundleIdentifier)); - System.IO.Directory.CreateDirectory(GooglePlayServices.SettingsDialog.PackageDir); - Resolver.DoResolution(svcSupport, GooglePlayServices.SettingsDialog.PackageDir, - HandleOverwriteConfirmation, - () => { - AssetDatabase.Refresh(); - if (resolutionComplete != null) resolutionComplete(); - }); - } - - /// - /// 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"); - - } - - /// - /// Add a menu item for resolving the jars manually. - /// - [MenuItem("Assets/Play Services Resolver/Android Resolver/Settings")] - public static void SettingsDialog() - { - if (Resolver != null) - { - Resolver.ShowSettingsDialog(); - } - else - { - DefaultResolver.ShowSettings(); - } - } - - /// - /// Add a menu item for resolving the jars manually. - /// - [MenuItem("Assets/Play Services Resolver/Android Resolver/Resolve Client Jars")] - public static void MenuResolve() - { - if (Resolver == null) { - NotAvailableDialog(); - return; - } - Resolve(() => { EditorUtility.DisplayDialog("Android Jar Dependencies", - "Resolution Complete", "OK"); }); - } - - /// - /// Handles the overwrite confirmation. - /// - /// true, if overwrite confirmation was handled, false otherwise. - /// Old dependency. - /// New dependency replacing old. - public static bool HandleOverwriteConfirmation(Dependency oldDep, Dependency newDep) - { - // Don't prompt overwriting the same version, just do it. - if (oldDep.BestVersion != newDep.BestVersion) - { - string msg = "Replace " + oldDep.Artifact + " version " + - oldDep.BestVersion + " with version " + newDep.BestVersion + "?"; - return EditorUtility.DisplayDialog("Android Jar Dependencies", - msg,"Replace","Keep"); - } - return true; - } - } -} diff --git a/source/PlayServicesResolver/src/ResolverVer1_1.cs b/source/PlayServicesResolver/src/ResolverVer1_1.cs deleted file mode 100644 index 4b7802de..00000000 --- a/source/PlayServicesResolver/src/ResolverVer1_1.cs +++ /dev/null @@ -1,752 +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 Google.JarResolver; - using System.Collections; - using System.IO; - using System.Text.RegularExpressions; - using System.Xml; - - [InitializeOnLoad] - 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 - { - // 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 = ""; - } - - // 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("Temp", "GoogleAarExplodeCache.xml"); - - private const int MajorVersion = 1; - private const int MinorVersion = 1; - private const int PointVersion = 0; - - static ResolverVer1_1() - { - if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) - { - ResolverVer1_1 resolver = new ResolverVer1_1(); - resolver.LoadAarExplodeCache(); - PlayServicesResolver.RegisterResolver(resolver); - } - } - - /// - /// 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; - foreach (var item in explodeData1) { - AarExplodeData data = null; - if (!explodeData2.TryGetValue(item.Key, out data)) return false; - if (!item.Value.Equals(data)) return false; - } - return true; - } - - /// - /// Load data cached in aarExplodeDataFile into aarExplodeData. - /// - private void LoadAarExplodeCache() - { - if (!File.Exists(aarExplodeDataFile)) return; - - 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(); - } - } - } - } while (!(reader.Name == "explodeData" && - reader.NodeType == XmlNodeType.EndElement)); - if (aar != "") aarExplodeData[aar] = aarData; - } - } - } - } - aarExplodeDataSaved = new Dictionary(aarExplodeData); - } - - /// - /// Save data from aarExplodeData into aarExplodeDataFile. - /// - private void SaveAarExplodeCache() - { - if (File.Exists(aarExplodeDataFile)) - { - // If the explode data hasn't been modified, don't save. - if (CompareExplodeData(aarExplodeData, aarExplodeDataSaved)) return; - File.Delete(aarExplodeDataFile); - } - 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(PlayerSettings.bundleIdentifier); - writer.WriteEndElement(); - writer.WriteStartElement("path"); - writer.WriteValue(kv.Value.path); - writer.WriteEndElement(); - writer.WriteEndElement(); - } - writer.WriteEndElement(); - writer.Flush(); - writer.Close(); - aarExplodeDataSaved = new Dictionary(aarExplodeData); - } - - /// - /// Find a tool in the Android SDK. - /// - /// PlayServicesSupport instance used to retrieve the SDK - /// path. - /// Name of the tool to search for. - /// String with the path to the tool if found, null otherwise. - internal static string FindAndroidSdkTool(PlayServicesSupport svcSupport, string toolName) - { - string toolPath = null; - string sdkPath = svcSupport.SDK; - if (sdkPath == null || sdkPath == "") - { - Debug.LogWarning(PlayServicesSupport.AndroidSdkConfigurationError + - " Will fallback to searching for " + toolName + - " in the system path."); - } - else - { - string[] extensions; - if (UnityEngine.RuntimePlatform.WindowsEditor == - UnityEngine.Application.platform) { - extensions = new string[] { CommandLine.GetExecutableExtension(), - ".bat", ".cmd" }; - } else { - extensions = new string[] { CommandLine.GetExecutableExtension() }; - } - foreach (var extension in extensions) { - toolPath = Path.Combine(sdkPath, Path.Combine("tools", toolName + extension)); - if (File.Exists(toolPath)) { - break; - } - } - } - if (toolPath == null || !File.Exists(toolPath)) - { - toolPath = CommandLine.FindExecutable(toolName); - } - return toolPath; - } - - /// - /// Generate an array from a string collection. - /// - /// An array of strings. - private static string[] CollectionToArray(ICollection enumerator) - { - return (string[])(new ArrayList(enumerator)).ToArray(typeof(string)); - } - - /// - /// Delegate called when GetAvailablePackages() completes. - /// - internal delegate void GetAvailablePackagesComplete(Dictionary packages); - - // Answers Android SDK manager license questions. - private class LicenseResponder : CommandLine.LineReader - { - private const string Question = "Do you accept the license"; - - private string response; - - // Initialize the class to respond "yes" or "no" to license questions. - public LicenseResponder(bool accept) - { - LineHandler += CheckAndRespond; - response = accept ? "yes" : "no"; - } - - // Respond license questions with the "response". - public void CheckAndRespond(System.Diagnostics.Process process, StreamWriter stdin, - CommandLine.StreamData data) - { - if (process.HasExited) return; - if ((data.data != null && data.text.Contains(Question)) || - CommandLine.LineReader.Aggregate(GetBufferedData(0)).text.Contains(Question)) - { - Flush(); - // Ignore I/O exceptions as this could race with the process exiting. - try - { - foreach (byte b in System.Text.Encoding.UTF8.GetBytes( - response + System.Environment.NewLine)) - { - stdin.BaseStream.WriteByte(b); - } - stdin.BaseStream.Flush(); - } - catch (System.IO.IOException) - { - } - } - } - } - - /// - /// Get the set of available SDK packages and whether they're installed. - /// - /// Path to the Android SDK manager tool. - /// PlayServicesSupport instance used to retrieve the SDK - /// path. - /// Delegate called with a dictionary of package names and whether - /// they're installed or null if the Android SDK isn't configured correctly. - internal static void GetAvailablePackages( - string androidTool, PlayServicesSupport svcSupport, - GetAvailablePackagesComplete complete) - { - CommandLineDialog window = CommandLineDialog.CreateCommandLineDialog( - "Get Installed Android SDK packages."); - window.modal = false; - window.summaryText = "Getting list of installed Android packages."; - window.progressTitle = window.summaryText; - window.autoScrollToBottom = true; - window.RunAsync( - androidTool, "list sdk -u -e -a", - (result) => { - window.Close(); - if (result.exitCode != 0) - { - Debug.LogError("Unable to determine which Android packages are " + - "installed. Failed to run " + androidTool + ". " + - result.stderr + " (" + result.exitCode.ToString() + ")"); - complete(null); - return; - } - Dictionary packages = new Dictionary(); - string[] lines = Regex.Split(result.stdout, "\r\n|\r|\n"); - string packageIdentifier = null; - foreach (string line in lines) - { - // Find the start of a package description. - Match match = Regex.Match(line, "^id:\\W+\\d+\\W+or\\W+\"([^\"]+)\""); - if (match.Success) - { - packageIdentifier = match.Groups[1].Value; - packages[packageIdentifier] = false; - continue; - } - if (packageIdentifier == null) - { - continue; - } - // Parse the install path and record whether the package is installed. - match = Regex.Match(line, "^\\W+Install[^:]+:\\W+([^ ]+)"); - if (match.Success) - { - packages[packageIdentifier] = File.Exists( - Path.Combine(Path.Combine(svcSupport.SDK, match.Groups[1].Value), - "source.properties")); - packageIdentifier = null; - } - } - complete(packages); - }, - maxProgressLines: 50); - window.Show(); - } - - #region IResolver implementation - - /// - /// Version of the resolver. - 1.1.0 - /// - /// The resolver with the greatest version is used when resolving. - /// The value of the verison is calcuated using MakeVersion in DefaultResolver - /// - public override int Version() - { - return MakeVersionNumber(MajorVersion, MinorVersion, PointVersion); - } - - /// - /// Perform the resolution and the exploding/cleanup as needed. - /// - public override void DoResolution( - PlayServicesSupport svcSupport, string destinationDirectory, - PlayServicesSupport.OverwriteConfirmation handleOverwriteConfirmation, - System.Action resolutionComplete) - { - System.Action resolve = () => { - DoResolutionNoAndroidPackageChecks(svcSupport, destinationDirectory, - handleOverwriteConfirmation); - resolutionComplete(); - }; - - var dependencies = svcSupport.DependenciesPresent(destinationDirectory); - if (dependencies == null) return; - - // Set of packages that need to be installed. - Dictionary installPackages = new Dictionary(); - // Retrieve the set of required packages and whether they're installed. - Dictionary> requiredPackages = - new Dictionary>(); - foreach (Dependency dependency in - svcSupport.LoadDependencies(true, keepMissing: true).Values) - { - if (dependency.PackageIds != null) - { - foreach (string packageId in dependency.PackageIds) - { - Dictionary dependencySet; - if (!requiredPackages.TryGetValue(packageId, out dependencySet)) - { - dependencySet = new Dictionary(); - } - dependencySet[dependency.Key] = false; - requiredPackages[packageId] = dependencySet; - // If the dependency is missing, add it to the set that needs to be - // installed. - if (System.String.IsNullOrEmpty(dependency.BestVersionPath)) - { - installPackages[packageId] = false; - } - } - } - } - - // 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 (string pkg in requiredPackages.Keys) - { - string depString = System.String.Join( - ", ", CollectionToArray(requiredPackages[pkg].Keys)); - if (installPackages.ContainsKey(pkg) && depString.Length > 0) - { - Debug.LogWarning(pkg + " not installed or out of date! This is " + - "required by the following dependencies " + depString); - } - } - // Attempt resolution. - resolve(); - return; - } - - // Find the Android SDK manager. - string sdkPath = svcSupport.SDK; - string androidTool = FindAndroidSdkTool(svcSupport, "android"); - if (androidTool == null || sdkPath == null || sdkPath == "") - { - Debug.LogError("Unable to find the Android SDK manager tool. " + - "Required Android packages (" + - System.String.Join(", ", CollectionToArray(installPackages.Keys)) + - ") can not be installed. " + - PlayServicesSupport.AndroidSdkConfigurationError); - return; - } - - // Get the set of available and installed packages. - GetAvailablePackages( - androidTool, svcSupport, - (Dictionary packageInfo) => { - if (packageInfo == null) - { - return; - } - - // Filter the set of packages to install by what is available. - foreach (string pkg in requiredPackages.Keys) - { - bool installed = false; - string depString = System.String.Join( - ", ", CollectionToArray(requiredPackages[pkg].Keys)); - if (packageInfo.TryGetValue(pkg, out installed)) - { - if (!installed) - { - installPackages[pkg] = false; - Debug.LogWarning(pkg + " not installed or out of date! " + - "This is required by the following " + - "dependencies " + depString); - } - } - else - { - Debug.LogWarning(pkg + " referenced by " + depString + - " not available in the Android SDK. This " + - "package will not be installed."); - installPackages.Remove(pkg); - } - } - - if (installPackages.Count == 0) - { - resolve(); - return; - } - - // Start installation. - string installPackagesString = System.String.Join( - ",", CollectionToArray(installPackages.Keys)); - string packagesCommand = "update sdk -a -u -t " + installPackagesString; - CommandLineDialog window = CommandLineDialog.CreateCommandLineDialog( - "Install Android SDK packages"); - window.summaryText = "Retrieving licenses..."; - window.modal = false; - window.progressTitle = window.summaryText; - window.RunAsync( - androidTool, packagesCommand, - (CommandLine.Result getLicensesResult) => { - // Get the start of the license text. - int licenseTextStart = getLicensesResult.stdout.IndexOf("--------"); - if (getLicensesResult.exitCode != 0 || licenseTextStart < 0) - { - window.Close(); - Debug.LogError("Unable to retrieve licenses for packages " + - installPackagesString); - return; - } - - // Remove the download output from the string. - string licenseText = getLicensesResult.stdout.Substring( - licenseTextStart); - window.summaryText = ("License agreement(s) required to install " + - "Android SDK packages"); - window.bodyText = licenseText; - window.yesText = "agree"; - window.noText = "decline"; - window.result = false; - window.Repaint(); - window.buttonClicked = (TextAreaDialog dialog) => { - if (!dialog.result) - { - window.Close(); - return; - } - - window.summaryText = "Installing Android SDK packages..."; - window.bodyText = ""; - window.yesText = ""; - window.noText = ""; - window.buttonClicked = null; - window.progressTitle = window.summaryText; - window.autoScrollToBottom = true; - window.Repaint(); - // Kick off installation. - ((CommandLineDialog)window).RunAsync( - androidTool, packagesCommand, - (CommandLine.Result updateResult) => { - window.Close(); - if (updateResult.exitCode == 0) - { - resolve(); - } - else - { - Debug.LogError("Android SDK update failed. " + - updateResult.stderr + "(" + - updateResult.exitCode.ToString() + ")"); - } - }, - ioHandler: (new LicenseResponder(true)).AggregateLine, - maxProgressLines: 500); - }; - }, - ioHandler: (new LicenseResponder(false)).AggregateLine, - maxProgressLines: 250); - }); - } - - public override void DoResolution( - PlayServicesSupport svcSupport, string destinationDirectory, - PlayServicesSupport.OverwriteConfirmation handleOverwriteConfirmation) - { - DoResolution(svcSupport, destinationDirectory, handleOverwriteConfirmation, - () => {}); - } - - /// - /// 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. - public override string[] OnBundleId(string bundleId) - { - // Determine which packages need to be updated. - List packagesToUpdate = new List(); - List aarsToResolve = new List(); - foreach (KeyValuePair kv in aarExplodeData) - { - if (kv.Value.bundleId != bundleId && kv.Value.path != "" && - ShouldExplode(kv.Value.path)) - { - packagesToUpdate.Add(kv.Value.path); - aarsToResolve.Add(kv.Key); - } - } - // 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); - return packagesToUpdate.Count > 0 ? packagesToUpdate.ToArray() : null; - } - - #endregion - - /// - /// Perform resolution with no Android package dependency checks. - /// - private void DoResolutionNoAndroidPackageChecks( - PlayServicesSupport svcSupport, string destinationDirectory, - PlayServicesSupport.OverwriteConfirmation handleOverwriteConfirmation) - { - try - { - // Get the collection of dependencies that need to be copied. - Dictionary deps = - svcSupport.ResolveDependencies(true, destDirectory: destinationDirectory, - explodeAar: (aarPath) => { - return ShouldExplode(aarPath); - }); - // Copy the list - svcSupport.CopyDependencies(deps, - destinationDirectory, - handleOverwriteConfirmation); - - } - catch (Google.JarResolver.ResolutionException e) - { - Debug.LogError(e.ToString()); - return; - } - - // we want to look at all the .aars to decide to explode or not. - // Some aars have variables in their AndroidManifest.xml file, - // e.g. ${applicationId}. Unity does not understand how to process - // these, so we handle it here. - ProcessAars(destinationDirectory); - - SaveAarExplodeCache(); - } - - /// - /// 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. - void ProcessAars(string dir) - { - string[] files = Directory.GetFiles(dir, "*.aar"); - foreach (string f in files) - { - string dirPath = Path.Combine(dir, Path.GetFileNameWithoutExtension(f)); - string targetPath = Path.Combine(dir, Path.GetFileName(f)); - if (ShouldExplode(f)) - { - ReplaceVariables(ProcessAar(Path.GetFullPath(dir), f)); - targetPath = dirPath; - } - else - { - // Clean up previously expanded / exploded versions of the AAR. - PlayServicesSupport.DeleteExistingFileOrDirectory(dirPath, - includeMetaFiles: true); - } - aarExplodeData[f].path = targetPath; - aarExplodeData[f].bundleId = PlayerSettings.bundleIdentifier; - } - } - - /// - /// Determined 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. - /// The aar file. - internal virtual bool ShouldExplode(string aarFile) - { - AarExplodeData aarData = null; - if (!aarExplodeData.TryGetValue(aarFile, out aarData)) aarData = new AarExplodeData(); - 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.GradleBuildEnabled && - PlayServicesResolver.ProjectExportEnabled && - !SettingsDialog.ExplodeAars) - { - explosionEnabled = false; - } - bool explode = false; - if (explosionEnabled) - { - explode = !SupportsAarFiles; - if (!explode) - { - System.DateTime modificationTime = File.GetLastWriteTime(aarFile); - if (modificationTime.CompareTo(aarData.modificationTime) <= 0) - { - explode = aarData.explode; - } - } - if (!explode) - { - string temporaryDirectory = CreateTemporaryDirectory(); - if (temporaryDirectory == null) return false; - string manifestFilename = "AndroidManifest.xml"; - try - { - if (ExtractAar(aarFile, new string[] {manifestFilename}, - temporaryDirectory)) - { - string manifestPath = Path.Combine(temporaryDirectory, - manifestFilename); - if (File.Exists(manifestPath)) - { - string manifest = File.ReadAllText(manifestPath); - explode = manifest.IndexOf("${applicationId}") >= 0; - } - aarData.modificationTime = File.GetLastWriteTime(aarFile); - } - } - catch (System.Exception e) - { - Debug.Log("Unable to examine AAR file " + aarFile + ", err: " + e); - throw e; - } - finally - { - PlayServicesSupport.DeleteExistingFileOrDirectory(temporaryDirectory); - } - } - } - aarData.explode = explode; - aarExplodeData[aarFile] = aarData; - return explode; - } - - /// - /// Replaces the variables in the AndroidManifest file. - /// - /// Exploded. - void ReplaceVariables(string exploded) - { - string manifest = Path.Combine(exploded, "AndroidManifest.xml"); - if (File.Exists(manifest)) - { - StreamReader sr = new StreamReader(manifest); - string body = sr.ReadToEnd(); - sr.Close(); - - body = body.Replace("${applicationId}", PlayerSettings.bundleIdentifier); - - using (var wr = new StreamWriter(manifest, false)) - { - wr.Write(body); - } - } - } - } -} - diff --git a/source/PlayServicesResolver/src/SettingsDialog.cs b/source/PlayServicesResolver/src/SettingsDialog.cs deleted file mode 100644 index 87545dae..00000000 --- a/source/PlayServicesResolver/src/SettingsDialog.cs +++ /dev/null @@ -1,167 +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.IO; - using UnityEditor; - using UnityEngine; - - /// - /// Settings dialog for PlayServices Resolver. - /// - public class SettingsDialog : EditorWindow - { - const string Namespace = "GooglePlayServices."; - internal const string AutoResolveKey = Namespace + "AutoResolverEnabled"; - internal const string PackageInstallKey = Namespace + "AndroidPackageInstallationEnabled"; - internal const string PackageDirKey = Namespace + "PackageDirectory"; - internal const string ExplodeAarsKey = Namespace + "ExplodeAars"; - - 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; - - internal static bool EnableAutoResolution { - set { EditorPrefs.SetBool(AutoResolveKey, value); } - get { return EditorPrefs.GetBool(AutoResolveKey, true); } - } - - internal static bool InstallAndroidPackages { - private set { EditorPrefs.SetBool(PackageInstallKey, value); } - get { return EditorPrefs.GetBool(PackageInstallKey, true); } - } - - internal static string PackageDir { - private set { EditorPrefs.SetString(PackageDirKey, value); } - get { - return ConfigurablePackageDir ? - ValidatePackageDir(EditorPrefs.GetString(PackageDirKey, DefaultPackageDir)) : - DefaultPackageDir; - } - } - - // Whether AARs that use variable expansion should be exploded when Gradle builds are - // enabled. - internal static bool ExplodeAars { - private set { EditorPrefs.SetBool(ExplodeAarsKey, value); } - get { return EditorPrefs.GetBool(ExplodeAarsKey, true); } - } - - internal static string ValidatePackageDir(string directory) { - if (!directory.StartsWith(AndroidPluginsDir)) { - directory = AndroidPluginsDir; - } - return directory; - } - - bool enableAutoResolution; - bool installAndroidPackages; - string packageDir; - bool explodeAars; - - public void Initialize() - { - minSize = new Vector2(300, 200); - position = new Rect(UnityEngine.Screen.width / 3, UnityEngine.Screen.height / 3, - minSize.x, minSize.y); - } - - public void OnEnable() - { - enableAutoResolution = EnableAutoResolution; - installAndroidPackages = InstallAndroidPackages; - packageDir = PackageDir; - explodeAars = ExplodeAars; - } - - /// - /// Called when the GUI should be rendered. - /// - public void OnGUI() - { - GUI.skin.label.wordWrap = true; - GUILayout.BeginVertical(); - - GUILayout.BeginHorizontal(); - GUILayout.Label("Enable Background Resolution", EditorStyles.boldLabel); - enableAutoResolution = EditorGUILayout.Toggle(enableAutoResolution); - GUILayout.EndHorizontal(); - - GUILayout.BeginHorizontal(); - GUILayout.Label("Install Android Packages", EditorStyles.boldLabel); - installAndroidPackages = EditorGUILayout.Toggle(installAndroidPackages); - GUILayout.EndHorizontal(); - - if (ConfigurablePackageDir) { - GUILayout.BeginHorizontal(); - string previousPackageDir = packageDir; - GUILayout.Label("Package Directory", EditorStyles.boldLabel); - if (GUILayout.Button("Browse")) { - string path = EditorUtility.OpenFolderPanel("Set Package Directory", - PackageDir, ""); - int startOfPath = path.IndexOf(AndroidPluginsDir); - if (startOfPath < 0) { - packageDir = ""; - } else { - packageDir = path.Substring(startOfPath, path.Length - startOfPath); - } - } - if (!previousPackageDir.Equals(packageDir)) { - packageDir = ValidatePackageDir(packageDir); - } - GUILayout.EndHorizontal(); - packageDir = EditorGUILayout.TextField(packageDir); - } - - GUILayout.BeginHorizontal(); - GUILayout.Label("Explode AARs", EditorStyles.boldLabel); - explodeAars = EditorGUILayout.Toggle(explodeAars); - GUILayout.EndHorizontal(); - if (explodeAars) { - GUILayout.Label("AARs will be exploded (unpacked) when ${applicationId} " + - "variable replacement is required in an AAR's " + - "AndroidManifest.xml."); - } 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."); - } - - GUILayout.Space(10); - GUILayout.BeginHorizontal(); - bool closeWindow = GUILayout.Button("Cancel"); - bool ok = GUILayout.Button("OK"); - closeWindow |= ok; - if (ok) - { - EnableAutoResolution = enableAutoResolution; - InstallAndroidPackages = installAndroidPackages; - if (ConfigurablePackageDir) PackageDir = packageDir; - ExplodeAars = explodeAars; - } - if (closeWindow) Close(); - GUILayout.EndHorizontal(); - GUILayout.EndVertical(); - } - } -} - diff --git a/source/PlayServicesResolver/src/TextAreaDialog.cs b/source/PlayServicesResolver/src/TextAreaDialog.cs deleted file mode 100644 index 0bd31c05..00000000 --- a/source/PlayServicesResolver/src/TextAreaDialog.cs +++ /dev/null @@ -1,160 +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 GooglePlayServices -{ - using UnityEditor; - using UnityEngine; - - /// - /// Window which displays a scrollable text area and two buttons at the bottom. - /// - public class TextAreaDialog : EditorWindow - { - /// - /// Delegate type, called when a button is clicked. - /// - public delegate void ButtonClicked(TextAreaDialog dialog); - - /// - /// Delegate called when a button is clicked. - /// - public ButtonClicked buttonClicked; - - /// - /// Whether this window should be modal. - /// NOTE: This emulates modal behavior by re-acquiring focus when it's lost. - /// - public bool modal = true; - - /// - /// Set the text to display in the summary area of the window. - /// - public string summaryText = ""; - - /// - /// Set the text to display on the "yes" (left-most) button. - /// - public string yesText = ""; - - /// - /// Set the text to display on the "no" (left-most) button. - /// - public string noText = ""; - - /// - /// Set the text to display in the scrollable text area. - /// - public string bodyText = ""; - - /// - /// Result of yes / no button press. true if the "yes" button was pressed, false if the - /// "no" button was pressed. Defaults to "false". - /// - public bool result = false; - - /// - /// Current position of the scrollbar. - /// - public Vector2 scrollPosition; - - /// - /// Get the existing text area window or create a new one. - /// - /// Title to display on the window. - /// Reference to this class - public static TextAreaDialog CreateTextAreaDialog(string title) - { - TextAreaDialog window = (TextAreaDialog)EditorWindow.GetWindow(typeof(TextAreaDialog), - true, title, true); - window.Initialize(); - return window; - } - - public virtual void Initialize() - { - yesText = ""; - noText = ""; - summaryText = ""; - bodyText = ""; - result = false; - scrollPosition = new Vector2(0, 0); - minSize = new Vector2(300, 200); - position = new Rect(UnityEngine.Screen.width / 3, UnityEngine.Screen.height / 3, - minSize.x * 2, minSize.y * 2); - } - - // Draw the GUI. - protected virtual void OnGUI() - { - GUILayout.BeginVertical(); - - GUILayout.Label(summaryText, EditorStyles.boldLabel); - - scrollPosition = GUILayout.BeginScrollView(scrollPosition); - // Unity text elements can only display up to a small X number of characters (rumors - // are ~65k) so generate a set of labels one for each subset of the text being - // displayed. - int bodyTextOffset = 0; - System.Collections.Generic.List bodyTextList = - new System.Collections.Generic.List(); - 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)); - bodyTextOffset += readSize; - } - foreach (string bodyTextChunk in bodyTextList) - { - GUILayout.Label(bodyTextChunk, EditorStyles.wordWrappedLabel); - } - GUILayout.EndScrollView(); - - bool yesPressed = false; - bool noPressed = false; - GUILayout.BeginHorizontal(); - if (yesText != "") yesPressed = GUILayout.Button(yesText); - if (noText != "") noPressed = GUILayout.Button(noText); - GUILayout.EndHorizontal(); - - GUILayout.EndVertical(); - - // If yes or no buttons were pressed, call the buttonClicked delegate. - if (yesPressed || noPressed) - { - if (yesPressed) - { - result = true; - } - else if (noPressed) - { - result = false; - } - if (buttonClicked != null) buttonClicked(this); - } - } - - // Optionally make the dialog modal. - protected virtual void OnLostFocus() - { - if (modal) Focus(); - } - } - -} 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/Properties/AssemblyInfo.cs b/source/VersionHandler/Properties/AssemblyInfo.cs index d496a3db..6cb7ef11 100644 --- a/source/VersionHandler/Properties/AssemblyInfo.cs +++ b/source/VersionHandler/Properties/AssemblyInfo.cs @@ -18,7 +18,7 @@ // Information about this assembly is defined by the following attributes. // Change them to the values specific to your project. -[assembly: AssemblyTitle("Google.JarResolver")] +[assembly: AssemblyTitle("Google.VersionHandler")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Google Inc.")] diff --git a/source/VersionHandler/VersionHandler.csproj b/source/VersionHandler/VersionHandler.csproj index 784c853f..47e193fc 100644 --- a/source/VersionHandler/VersionHandler.csproj +++ b/source/VersionHandler/VersionHandler.csproj @@ -5,7 +5,7 @@ AnyCPU 8.0.30703 2.0 - {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} + {5378B37A-887E-49ED-A8AE-42FA843AA9DC} Library Google Google.VersionHandler @@ -23,7 +23,8 @@ False - none + True + full True bin\Release DEBUG;UNITY_EDITOR @@ -52,7 +53,6 @@ - diff --git a/source/VersionHandler/src/SettingsDialog.cs b/source/VersionHandler/src/SettingsDialog.cs deleted file mode 100644 index fcc508e1..00000000 --- a/source/VersionHandler/src/SettingsDialog.cs +++ /dev/null @@ -1,94 +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 { - -using UnityEditor; -using UnityEngine; - -/// -/// Settings dialog for VersionHandler. -/// -public class SettingsDialog : EditorWindow -{ - /// - /// Whether the version handler is enabled. - /// - internal bool enabled; - - /// - /// Whether to prompt the user before deleting obsolete files. - /// - internal bool cleanUpPromptEnabled; - - /// - /// Whether to enable / disable verbose logging. - /// - internal bool verboseLoggingEnabled; - - /// - /// Setup the window's initial position and size. - /// - public void Initialize() - { - minSize = new Vector2(300, 200); - position = new Rect(UnityEngine.Screen.width / 3, - UnityEngine.Screen.height / 3, - minSize.x, minSize.y); - } - - /// - /// Called when the window is loaded. - /// - public void OnEnable() - { - enabled = VersionHandler.Enabled; - cleanUpPromptEnabled = VersionHandler.CleanUpPromptEnabled; - verboseLoggingEnabled = VersionHandler.VerboseLoggingEnabled; - } - - /// - /// Called when the GUI should be rendered. - /// - public void OnGUI() - { - GUI.skin.label.wordWrap = true; - GUILayout.BeginVertical(); - GUILayout.Label("Enable version management", EditorStyles.boldLabel); - enabled = EditorGUILayout.Toggle(enabled); - GUILayout.Label("Prompt for obsolete file deletion", - EditorStyles.boldLabel); - cleanUpPromptEnabled = EditorGUILayout.Toggle(cleanUpPromptEnabled); - GUILayout.Label("Verbose logging", EditorStyles.boldLabel); - verboseLoggingEnabled = EditorGUILayout.Toggle(verboseLoggingEnabled); - GUILayout.Space(10); - if (GUILayout.Button("OK")) { - VersionHandler.Enabled = enabled; - VersionHandler.CleanUpPromptEnabled = cleanUpPromptEnabled; - VersionHandler.VerboseLoggingEnabled = verboseLoggingEnabled; - Close(); - // If the handler has been enabled, refresh the asset database - // to force it to run. - if (enabled) { - AssetDatabase.Refresh(); - } - } - GUILayout.EndVertical(); - } -} - -} // namespace Google - diff --git a/source/VersionHandler/src/VersionHandler.cs b/source/VersionHandler/src/VersionHandler.cs index 1c79d506..0b0f719e 100644 --- a/source/VersionHandler/src/VersionHandler.cs +++ b/source/VersionHandler/src/VersionHandler.cs @@ -14,1078 +14,327 @@ // limitations under the License. // -using UnityEngine; -using UnityEditor; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Reflection; +using System.Text.RegularExpressions; using System; +using UnityEngine; +using UnityEditor; + namespace Google { +/// +/// Enables the most recent version of the VersionHandler dll and provides an interface to +/// the VersionHandler's implementation. +/// [InitializeOnLoad] -public class VersionHandler : AssetPostprocessor { - /// - /// Derives metadata from an asset filename. - /// - public class FileMetadata { - // Splits a filename into components. - private class FilenameComponents - { - // Name of the file. - public string filename; - // Directory component. - public string directory; - // Basename (filename with no directory). - public string basename; - // Extension component. - public string extension; - // Basename without an extension. - public string basenameNoExtension; - - // Parse a filename into components. - public FilenameComponents(string filename) { - this.filename = filename; - directory = Path.GetDirectoryName(filename); - basename = Path.GetFileName(filename); - extension = Path.GetExtension(basename); - basenameNoExtension = - basename.Substring(0, basename.Length - extension.Length); - } - } - - - // Separator for metadata tokens in the supplied filename. - private static char[] FILENAME_TOKEN_SEPARATOR = new char[] { '_' }; - // Separator for fields in each metadata token in the supplied - // filename or label. - private static char[] FIELD_SEPARATOR = new char[] { '-' }; - - // Prefix which identifies the targets metadata in the filename or - // asset label. - private static string TOKEN_TARGETS = "t"; - // Prefix which identifies the version metadata in the filename or - // asset label. - private static string TOKEN_VERSION = "v"; - // Prefix which indicates this file is a package manifest. - private static string FILENAME_TOKEN_MANIFEST = "manifest"; - - // Delimiter for version numbers. - private static char[] VERSION_DELIMITER = new char[] { '.' }; - // Maximum number of components parsed from a version number. - private static int MAX_VERSION_COMPONENTS = 4; - // Multiplier applied to each component of the version number, - // see CalculateVersion(). - private static long VERSION_COMPONENT_MULTIPLIER = 1000; - // Prefix for labels which encode metadata of an asset. - private static string LABEL_PREFIX = "gvh_"; - // Initialized depending on the version of unity we are running against - private static HashSet targetBlackList = null; - // Initialized by parsing BuildTarget enumeration values from - // BUILD_TARGET_NAME_TO_ENUM_NAME. - private static Dictionary - buildTargetNameToEnum = null; - - /// - /// Label which flags whether an asset is should be managed by this - /// module. - /// - public static string ASSET_LABEL = "gvh"; - - // Map of build target names to BuildTarget enumeration names. - // We don't use BuildTarget enumeration values here as Unity has a - // habit of removing unsupported ones from the API. - static public Dictionary - BUILD_TARGET_NAME_TO_ENUM_NAME = new Dictionary { - {"osx", "StandaloneOSXUniversal"}, - {"osxintel", "StandaloneOSXIntel"}, - {"windows", "StandaloneWindows"}, - {"ios", "iOS"}, - {"ps3", "PS3"}, - {"xbox360", "XBOX360"}, - {"android", "Android"}, - {"linux32", "StandaloneLinux"}, - {"windows64", "StandaloneWindows64"}, - {"webgl", "WebGL"}, - {"linux64", "StandaloneLinux64"}, - {"linux", "StandaloneLinuxUniversal"}, - {"osxintel64", "StandaloneOSXIntel64"}, - {"tizen", "Tizen"}, - {"psp2", "PSP2"}, - {"ps4", "PS4"}, - {"xboxone", "XboxOne"}, - {"samsungtv", "SamsungTV"}, - {"nintendo3ds", "Nintendo3DS"}, - {"wiiu", "WiiU"}, - {"tvos", "tvOS"}, - }; - - /// - /// Get a set of build target names mapped to supported BuildTarget - /// enumeration values. - /// - internal static Dictionary GetBuildTargetNameToEnum() { - if (buildTargetNameToEnum == null) { - var targetBlackList = GetBlackList(); - buildTargetNameToEnum = - new Dictionary(); - foreach (var targetNameEnumName in - BUILD_TARGET_NAME_TO_ENUM_NAME) { - // Attempt to parse the build target name. - // ArgumentException, OverflowException or - // TypeInitializationException - // will be thrown if the build target is no longer - // supported. - BuildTarget target; - try { - target = (BuildTarget)Enum.Parse( - typeof(BuildTarget), targetNameEnumName.Value); - } catch (ArgumentException) { - continue; - } catch (OverflowException) { - continue; - } catch (TypeInitializationException) { - continue; - } - if (!targetBlackList.Contains(target)) { - buildTargetNameToEnum[targetNameEnumName.Key] = - target; - } - } - } - return buildTargetNameToEnum; - } - - // Returns the major/minor version of the unity environment we are running in - // as a float so it can be compared numerically. - static float GetUnityVersionMajorMinor() { - float result = 5.4f; - string version = Application.unityVersion; - if (!string.IsNullOrEmpty(version)) { - int dotIndex = version.IndexOf('.'); - if (dotIndex > 0 && version.Length > dotIndex + 1) { - if (!float.TryParse(version.Substring(0, dotIndex + 2), out result)) { - result = 5.4f; - } - } - } - return result; - } +public class VersionHandler { + const string VERSION_HANDLER_ASSEMBLY_NAME = "Google.VersionHandlerImpl"; + const string VERSION_HANDLER_IMPL_CLASS = "Google.VersionHandlerImpl"; + static Regex VERSION_HANDLER_FILENAME_RE = new Regex( + String.Format(".*[\\/]({0})(.*)(\\.dll)$", + VERSION_HANDLER_ASSEMBLY_NAME.Replace(".", "\\.")), + RegexOptions.IgnoreCase); + + // File which indicates boot strapping is in progress. + const string BOOT_STRAPPING_PATH = "Temp/VersionHandlerBootStrapping"; + // Value written to the boot strapping file to indicate the process is executing. + const string BOOT_STRAPPING_COMMAND = "BootStrapping"; + // File which contains the set of methods to call when an update operation is complete. + const string CALLBACKS_PATH = "Temp/VersionHandlerCallbacks"; + + // Enumerating over loaded assemblies and retrieving each name is pretty expensive (allocates + // ~2Kb per call multiplied by number of assemblies (i.e > 50)). This leads to memory being + // allocated that needs to be garbage collected which can reduce performance of the editor. + // This caches any found types for each combined assembly name + class name. Once a class + // is found this dictionary retains a reference to the type. + private static Dictionary typeCache = new Dictionary(); + + // Get the VersionHandler implementation class. + private static Type Impl { + get { return FindClass(VERSION_HANDLER_ASSEMBLY_NAME, VERSION_HANDLER_IMPL_CLASS); } + } - // Returns a hashset containing blacklisted build targets for the current - // unity environment. - // We need to maintain a seperate blacklist as Unity occasionally - // removes BuildTarget display names but does not remove the enumeration - // values associated with the names. This causes a fatal error in - // PluginImporter.GetCompatibleWithPlatform() when provided with a - // BuildTarget that no longer has a display name. - static HashSet GetBlackList() { - if (targetBlackList == null) { - targetBlackList = new HashSet(); - if (GetUnityVersionMajorMinor() >= 5.5) { - targetBlackList.Add(BuildTarget.PS3); - targetBlackList.Add(BuildTarget.XBOX360); - } - } - return targetBlackList; + // Get the VersionHandler implementation class, attempting to bootstrap the module first. + private static Type BootStrappedImpl { + get { + if (Impl == null) BootStrap(); + return Impl; } + } - /// - /// Name of the file use to construct this object. - /// - public string filename = ""; - - /// - /// Name of the file with metadata stripped. - /// - public string filenameCanonical = ""; - - /// - /// Version string parsed from the filename or AssetDatabase label if - /// it's not present in the filename. - /// - public string versionString = ""; - - /// - /// List of target platforms parsed from the filename. - /// - public string[] targets = null; - - /// - /// Set if this references an asset manifest. - /// - public bool isManifest = false; - - /// - /// Parse metadata from filename and store in this class. - /// - /// Name of the file to parse. - public FileMetadata(string filename) { - this.filename = filename; - filenameCanonical = filename; - - var filenameComponents = new FilenameComponents(filename); - // Parse metadata from the filename. - string[] tokens = - filenameComponents.basenameNoExtension.Split( - FILENAME_TOKEN_SEPARATOR); - if (tokens.Length > 1) { - filenameComponents.basenameNoExtension = tokens[0]; - for (int i = 1; i < tokens.Length; ++i) { - string token = tokens[i]; - if (token == FILENAME_TOKEN_MANIFEST) { - isManifest = true; - } else if (token.StartsWith(TOKEN_TARGETS)) { - targets = ParseTargets(token); - } else if (token.StartsWith(TOKEN_VERSION)) { - versionString = ParseVersion(token); - } - } - } - // Parse metadata from asset labels if it hasn't been specified in - // the filename. - AssetImporter importer = GetAssetImporter(); - if (importer != null) { - foreach (string label in AssetDatabase.GetLabels(importer)) { - // Labels are converted to title case in the asset database - // so convert to lower case before parsing. - string lowerLabel = label.ToLower(); - if (lowerLabel.StartsWith(LABEL_PREFIX)) { - string token = - lowerLabel.Substring(LABEL_PREFIX.Length); - if (token.StartsWith(TOKEN_TARGETS)) { - if (targets == null) { - targets = ParseTargets(token); - } - } else if (token.StartsWith(TOKEN_VERSION)) { - if (String.IsNullOrEmpty(versionString)) { - versionString = ParseVersion(token); - } - } else if (token.Equals(FILENAME_TOKEN_MANIFEST)) { - isManifest = true; - } + // Whether the VersionHandler implementation is being boot strapped. + private static bool BootStrapping { + get { + return File.Exists(BOOT_STRAPPING_PATH); + } + + set { + var currentlyBootStrapping = BootStrapping; + if (value != currentlyBootStrapping) { + if (value) { + AddToBootStrappingFile(new List { BOOT_STRAPPING_COMMAND }); + } else if (currentlyBootStrapping) { + // Forward any deferred properties. + UpdateCompleteMethods = UpdateCompleteMethodsInternal; + // Execute any scheduled method calls. + var duplicates = new HashSet(); + var executionList = new List(); + foreach (var command in ReadBootStrappingFile()) { + if (command == BOOT_STRAPPING_COMMAND) continue; + if (duplicates.Contains(command)) continue; + duplicates.Add(command); + executionList.Add(command); } - } - } - - // On Windows the AssetDatabase converts native path separators - // used by the .NET framework '\' to *nix style '/' such that - // System.IO.Path generated paths will not match those looked up - // in the asset database. So we convert the output of Path.Combine - // here to use *nix style paths so that it's possible to perform - // simple string comparisons to check for path equality. - filenameCanonical = Path.Combine( - filenameComponents.directory, - filenameComponents.basenameNoExtension + - filenameComponents.extension).Replace('\\', '/'); - UpdateAssetLabels(); - } - - /// - /// Parse version from a filename or label field. - /// - /// String to parse. Should start with - /// TOKEN_VERSION - /// Version string parsed from the token. - private static string ParseVersion(string token) { - return token.Substring(TOKEN_VERSION.Length); - } - - /// - /// Parse target names from a filename or label field. - /// - /// String to parse. Should start with - /// TOKEN_TARGETS - /// List of target names parsed from the token. - private static string[] ParseTargets(string token) { - string[] parsedTargets = - token.Substring(TOKEN_TARGETS.Length).Split(FIELD_SEPARATOR); - // Convert all target names to lower case. - string[] targets = new string[parsedTargets.Length]; - for (int i = 0; i < parsedTargets.Length; ++i) { - targets[i] = parsedTargets[i].ToLower(); - } - return targets; - } - - /// - /// Determine whether this file is compatible with the editor. - /// This is a special case as the editor isn't a "platform" covered - /// by UnityEditor.BuildTarget. - /// - /// true if this file targets the editor, false - /// otherwise. - public bool GetEditorEnabled() { - return targets != null && Array.IndexOf(targets, "editor") >= 0; - } - - /// - /// Get the list of build targets this file is compatible with. - /// - /// Set of BuildTarget (platforms) this is compatible with. - /// - public HashSet GetBuildTargets() { - HashSet buildTargetSet = new HashSet(); - var buildTargetToEnum = GetBuildTargetNameToEnum(); - if (targets != null) { - foreach (string target in targets) { - BuildTarget buildTarget; - if (buildTargetToEnum.TryGetValue(target, out buildTarget)) { - buildTargetSet.Add(buildTarget); - } else if (!target.Equals("editor")) { - UnityEngine.Debug.LogError( - filename + " reference to unknown target " + - target + " the version handler may out of date."); + while (executionList.Count > 0) { + var command = executionList[0]; + executionList.RemoveAt(0); + // Rewrite the list just to handle the case where this assembly gets + // reloaded. + File.WriteAllText(BOOT_STRAPPING_PATH, + String.Join("\n", executionList.ToArray())); + InvokeImplMethod(command); } + UpdateCompleteMethodsInternal = null; + // Clean up the boot strapping file. + File.Delete(BOOT_STRAPPING_PATH); } - - } - return buildTargetSet; - } - - /// - /// Save metadata from this class into the asset's labels. - /// - public void UpdateAssetLabels() { - AssetImporter importer = AssetImporter.GetAtPath(filename); - List labels = new List(); - // Strip labels we're currently managing. - foreach (string label in AssetDatabase.GetLabels(importer)) { - if (!(label.ToLower().StartsWith(LABEL_PREFIX) || - label.ToLower().Equals(ASSET_LABEL))) { - labels.Add(label); - } - } - // Add / preserve the label that indicates this asset is managed by - // this module. - labels.Add(ASSET_LABEL); - // Add labels for the metadata in this class. - if (!String.IsNullOrEmpty(versionString)) { - labels.Add(LABEL_PREFIX + TOKEN_VERSION + versionString); - } - if (targets != null && targets.Length > 0) { - labels.Add(LABEL_PREFIX + TOKEN_TARGETS + - String.Join(Char.ToString(FIELD_SEPARATOR[0]), - targets)); - } - if (isManifest) { - labels.Add(LABEL_PREFIX + FILENAME_TOKEN_MANIFEST); - } - AssetDatabase.SetLabels(importer, labels.ToArray()); - } - - /// - /// Get the AssetImporter associated with this file. - /// - /// AssetImporter instance if one is associated with this - /// file, null otherwise. - public AssetImporter GetAssetImporter() { - return AssetImporter.GetAtPath(filename); - } - - /// - /// Rename the file associated with this data. - /// - /// New name of the file. - /// true if successful, false otherwise. - public bool RenameAsset(string newFilename) { - var filenameComponents = new FilenameComponents(newFilename); - Debug.Assert(filenameComponents.directory == - Path.GetDirectoryName(filename)); - // If the target file exists, delete it. - if (AssetImporter.GetAtPath(newFilename) != null) { - if (!AssetDatabase.MoveAssetToTrash(newFilename)) { - UnityEngine.Debug.LogError( - "Failed to move asset to trash: " + filename); - return false; - } - } - try { - string error = AssetDatabase.RenameAsset( - filename, filenameComponents.basenameNoExtension); - if (!String.IsNullOrEmpty(error)) { - UnityEngine.Debug.LogError( - "Failed to rename asset " + filename + " to " + - newFilename + " (" + error + ")"); - return false; - } - AssetDatabase.ImportAsset(newFilename); - } catch (Exception) { - // Unity 5.3 and below can end up throw all sorts of - // exceptions here when attempting to reload renamed - // assemblies. Since these are completely harmless as - // everything will be reloaded and exceptions will be - // reported upon AssetDatabase.Refresh(), ignore them. - } - filename = newFilename; - UpdateAssetLabels(); - return true; - } - - - /// - /// Get a numeric version number. Each component is multiplied by - /// VERSION_COMPONENT_MULTIPLIER^(MAX_VERSION_COMPONENTS - - /// (component_index + 1)) - /// and accumulated in the returned value. - /// If the version string contains more than MAX_VERSION_COMPONENTS the - /// remaining components are ignored. - /// - /// 64-bit version number. - public long CalculateVersion() { - return CalculateVersion(versionString); - } - - /// - /// Get a numeric version number. Each component is multiplied by - /// VERSION_COMPONENT_MULTIPLIER^(MAX_VERSION_COMPONENTS - - /// (component_index + 1)) - /// and accumulated in the returned value. - /// If the version string contains more than MAX_VERSION_COMPONENTS the - /// remaining components are ignored. - /// - /// Version string to parse. - /// 64-bit version number. - public static long CalculateVersion(string versionString) { - long versionNumber = 0; - if (versionString.Length > 0) { - string[] components = versionString.Split(VERSION_DELIMITER); - int numberOfComponents = - components.Length < MAX_VERSION_COMPONENTS ? - components.Length : MAX_VERSION_COMPONENTS; - for (int i = 0; i < numberOfComponents; ++i) { - versionNumber += - Convert.ToInt64(components[i]) * - (long)Math.Pow( - (double)VERSION_COMPONENT_MULTIPLIER, - (double)(MAX_VERSION_COMPONENTS - (i + 1))); - } - } - return versionNumber; - } - - /// - /// Convert a numeric version back to a version string. - /// - /// Numeric version number. - /// Version string. - public static string VersionNumberToString(long versionNumber) { - List components = new List(); - for (int i = 0; i < MAX_VERSION_COMPONENTS; ++i) { - long componentDivisor = - (long)Math.Pow((double)VERSION_COMPONENT_MULTIPLIER, - (double)(MAX_VERSION_COMPONENTS - (i + 1))); - components.Add((versionNumber / componentDivisor).ToString()); - versionNumber %= componentDivisor; - } - return String.Join(Char.ToString(VERSION_DELIMITER[0]), - components.ToArray()); } } /// - /// Set of FileMetadata ordered by version. + /// Schedule the process of enabling the version handler. + /// In Unity 4.x it's not possible to enable a plugin DLL in a static constructor as it + /// crashes the editor. /// - public class FileMetadataByVersion { - /// - /// Name of the file with metadata removed. - /// - public string filenameCanonical = null; - - /// - /// Dictionary of FileMetadata ordered by version. - /// - private SortedDictionary metadataByVersion = - new SortedDictionary(); - - /// - /// Get the FileMetadata from this object ordered by version number. - /// - public SortedDictionary.ValueCollection Values { - get { return metadataByVersion.Values; } - } - - /// - /// Get FileMetadata from this object given a version number. - /// - /// Version to search for. - /// FileMetadata instance if the version is found, null - /// otherwise. - public FileMetadata this[long version] { - get { - FileMetadata metadata; - if (metadataByVersion.TryGetValue(version, out metadata)) { - return metadata; - } - return null; - } - } - - /// - /// Determine whether the PluginImporter class is available in - /// UnityEditor. Unity 4 does not have the PluginImporter class so - /// it's not possible to modify asset metadata without hacking the - /// .meta yaml files directly to enable / disable plugin targeting. - /// - internal static bool PluginImporterAvailable { - get { - return FindClass("UnityEditor", "UnityEditor.PluginImporter") != null; + static VersionHandler() { + // Schedule the process if the version handler isn't disabled on the command line. + 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; } } + } - /// - /// Construct an instance. - /// - /// Filename with metadata stripped. - /// - public FileMetadataByVersion(string filenameCanonical) { - this.filenameCanonical = filenameCanonical; - } - - /// - /// Add metadata to the set. - /// - public void Add(FileMetadata metadata) { - System.Diagnostics.Debug.Assert( - filenameCanonical == null || - metadata.filenameCanonical.Equals(filenameCanonical)); - metadataByVersion[metadata.CalculateVersion()] = metadata; - } - - /// - /// If this instance references a set of plugins, enable the most - /// recent versions. - /// - /// true if any plugin metadata was modified and requires an - /// AssetDatabase.Refresh(), false otherwise. - public bool EnableMostRecentPlugins() { - bool modified = false; - int versionIndex = 0; - int numberOfVersions = metadataByVersion.Count; - var disabledVersions = new List(); - string enabledVersion = null; + // Add a line to the boot strapping file. + private static void AddToBootStrappingFile(List lines) { + File.AppendAllText(BOOT_STRAPPING_PATH, String.Join("\n", lines.ToArray()) + "\n"); + } - // If the canonical file is out of date, update it. - if (numberOfVersions > 0) { - FileMetadata mostRecentVersion = - (FileMetadata)((new ArrayList(metadataByVersion.Values)) - [numberOfVersions - 1]); - if (mostRecentVersion.filename != filenameCanonical) { - FileMetadata canonicalMetadata = null; - foreach (var metadata in metadataByVersion.Values) { - if (metadata.filename == filenameCanonical) { - canonicalMetadata = metadata; - break; - } - } - if (!mostRecentVersion.RenameAsset(filenameCanonical)) { - return false; - } - if (canonicalMetadata != null) { - // Overwrote the current version with the rename - // operation. - metadataByVersion.Remove( - canonicalMetadata.CalculateVersion()); - numberOfVersions = metadataByVersion.Count; - } - modified = true; - } - } + // Read lines from the boot strapping file. + private static IEnumerable ReadBootStrappingFile() { + return File.ReadAllLines(BOOT_STRAPPING_PATH); + } - // Configure targeting for each revision of the plugin. - foreach (FileMetadata metadata in metadataByVersion.Values) { - versionIndex++; - PluginImporter pluginImporter = null; + /// + /// Enable the latest VersionHandler DLL if it's not already loaded. + /// + private static void BootStrap() { + var bootStrapping = BootStrapping; + var implAvailable = Impl != null; + // If the VersionHandler assembly is already loaded or we're still bootstrapping we have + // nothing to do. + if (bootStrapping) { + BootStrapping = !implAvailable; + return; + } + EditorApplication.update -= BootStrap; + if (implAvailable) return; + + var assemblies = new List(); + 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); + } + if (assemblies.Count == 0) { + UnityEngine.Debug.LogWarning(String.Format("No {0} DLL found to bootstrap", + VERSION_HANDLER_ASSEMBLY_NAME)); + return; + } + // Sort assembly paths by version number. + string mostRecentAssembly = null; + var mostRecentVersionNumber = -1; + foreach (var match in assemblies) { + var filename = match.Groups[0].Value; + var version = match.Groups[2].Value; + // Convert a multi-component version number to a string. + var components = version.Split(new [] { '.' }); + Array.Reverse(components); + var versionNumber = 0; + var componentMultiplier = 1000; + var currentComponentMultiplier = 1; + foreach (var component in components) { try { - pluginImporter = - (PluginImporter)metadata.GetAssetImporter(); - } catch (InvalidCastException) { - continue; + versionNumber += Int32.Parse(component) * currentComponentMultiplier; + } catch (FormatException) { + // Ignore the component. } - bool editorEnabled = metadata.GetEditorEnabled(); - var selectedTargets = metadata.GetBuildTargets(); - bool modifiedThisVersion = false; - // Only enable the most recent plugin - SortedDictionary - // orders keys in ascending order. - bool obsoleteVersion = (numberOfVersions > 1 && - versionIndex < numberOfVersions); - // If this is an obsolete version. - if (obsoleteVersion) { - // Disable for all platforms and the editor. - editorEnabled = false; - selectedTargets = new HashSet(); - } else { - // Track the current version. - enabledVersion = metadata.versionString; - } - // Enable / disable editor and platform settings. - if (pluginImporter.GetCompatibleWithEditor() != - editorEnabled) { - pluginImporter.SetCompatibleWithEditor(editorEnabled); - modifiedThisVersion = true; - } - foreach (BuildTarget target in - FileMetadata.GetBuildTargetNameToEnum().Values) { - bool enabled = selectedTargets != null && - selectedTargets.Contains(target); - try { - if (pluginImporter.GetCompatibleWithPlatform(target) != - enabled) { - pluginImporter.SetCompatibleWithPlatform( - target, enabled); - modifiedThisVersion = true; - } - } - catch(Exception e) { - UnityEngine.Debug.LogWarning( - "Unexpected error enumerating targets: " + e.Message); - } - } - if (modifiedThisVersion) { - pluginImporter.SaveAndReimport(); - } - // If the version was modified and it's obsolete keep track of - // it to log it later. - if (obsoleteVersion && modifiedThisVersion) { - disabledVersions.Add(metadata.versionString); - } - modified |= modifiedThisVersion; + currentComponentMultiplier *= componentMultiplier; } - // Log the versions that have been disabled and the version that - // has been enabled. - if (modified && enabledVersion != null && - VersionHandler.VerboseLoggingEnabled) { - string message = (filenameCanonical + ": enabled version " + - enabledVersion); - if (disabledVersions.Count > 0) { - message += (" obsolete versions disabled (" + - String.Join(", ", disabledVersions.ToArray()) + - ")"); - } - UnityEngine.Debug.Log(message); + if (versionNumber > mostRecentVersionNumber) { + mostRecentVersionNumber = versionNumber; + mostRecentAssembly = filename; } - return modified; } - - /// - /// Get all versions older than the newest version of each file with - /// multiple versions specified in its' metadata. - /// - /// Set of obsolete files. - public HashSet FindObsoleteVersions() { - HashSet obsoleteFiles = new HashSet(); - int versionIndex = 0; - int numberOfVersions = Values.Count; - foreach (var metadata in Values) { - versionIndex++; - if (versionIndex < numberOfVersions) { - obsoleteFiles.Add(metadata.filename); - } - } - return obsoleteFiles; + if (String.IsNullOrEmpty(mostRecentAssembly)) { + UnityEngine.Debug.LogWarning(String.Format("Failed to get the most recent {0} DLL. " + + "Unable to bootstrap.", + VERSION_HANDLER_ASSEMBLY_NAME)); + return; + } + BootStrapping = true; + if (VersionHandler.FindClass("UnityEditor", "UnityEditor.PluginImporter") != null) { + EnableEditorPlugin(mostRecentAssembly); + } else { + ReimportPlugin(mostRecentAssembly); } } /// - /// Set of FileMetadata grouped by filename with metadata stripped. - /// For example, "stuff_tEditor_v1.0.0.dll" and "stuff_tEditor_v1.0.1.dll" - /// will be referenced by FileMetadataVersions using the key "stuff.dll". + /// Force import a plugin by deleting metadata associated with the plugin. /// - public class FileMetadataSet { - /// - /// Dictionary of FileMetadataVersions indexed by filename with - /// metadata stripped. - /// - private Dictionary - metadataByCanonicalFilename = - new Dictionary(); - - /// - /// Get the FileMetadataByVersion for each filename bucket in this set. - /// - public Dictionary.ValueCollection - Values { - get { return metadataByCanonicalFilename.Values; } - } - - /// - /// Construct an instance. - /// - public FileMetadataSet() { } - - /// - /// Add file metadata to the set. - /// - public void Add(FileMetadata metadata) { - FileMetadataByVersion metadataByVersion; - string filenameCanonical = metadata.filenameCanonical; - if (!metadataByCanonicalFilename.TryGetValue( - filenameCanonical, out metadataByVersion)) { - metadataByVersion = - new FileMetadataByVersion(filenameCanonical); - } - metadataByVersion.Add(metadata); - metadataByCanonicalFilename[filenameCanonical] = metadataByVersion; - } - - /// - /// For each plugin (DLL) referenced by this set, disable targeting - /// for all versions and re-enable platform targeting for the most - /// recent version. - /// - /// true if any plugin metadata was modified and requires an - /// AssetDatabase.Refresh(), false otherwise. - public bool EnableMostRecentPlugins() { - bool modified = false; - - // If PluginImporter isn't available it's not possible - // to enable / disable targeting. - if (!FileMetadataByVersion.PluginImporterAvailable) return false; - - foreach (var metadataByVersion in - metadataByCanonicalFilename.Values) { - modified |= metadataByVersion.EnableMostRecentPlugins(); - } - return modified; - } - - /// - /// Parse metadata from a set of filenames. - /// - /// Filenames to parse. - /// FileMetadataSet referencing metadata parsed from filenames - /// ordered by version and bucketed by canonical filename. - /// - public static FileMetadataSet ParseFromFilenames(string[] filenames) { - FileMetadataSet metadataSet = new FileMetadataSet(); - // Parse metadata from filenames and bucket by version. - foreach (string filename in filenames) { - metadataSet.Add(new FileMetadata(filename)); - } - return metadataSet; - } - - /// - /// Filter the a set for files which have multiple versions and those - /// with metadata that selects the set of target platforms. - /// - /// Set to filter. - /// Filtered MetadataSet. - public static FileMetadataSet FindWithPendingUpdates( - FileMetadataSet metadataSet) { - FileMetadataSet outMetadataSet = new FileMetadataSet(); - foreach (var filenameAndMetadata in - metadataSet.metadataByCanonicalFilename) { - var metadataByVersion = filenameAndMetadata.Value.Values; - bool needsUpdate = metadataByVersion.Count > 1; - if (!needsUpdate) { - foreach (var metadata in metadataByVersion) { - if ((metadata.targets != null && - metadata.targets.Length > 0) || - metadata.isManifest) { - needsUpdate = true; - break; - } - } - } - if (needsUpdate) { - outMetadataSet.metadataByCanonicalFilename[ - filenameAndMetadata.Key] = filenameAndMetadata.Value; - } - } - return outMetadataSet; - } - - /// - /// Search for metadata for an existing file given a canonical filename - /// and version. - /// - /// Name of the file set to search - /// for. - /// Version number of the file in the set. - /// Reference to the metadata if successful, null otherwise. - /// - public FileMetadata FindMetadata(string filenameCanonical, - long version) { - FileMetadataByVersion metadataByVersion; - if (!metadataByCanonicalFilename.TryGetValue( - filenameCanonical, out metadataByVersion)) { - return null; - } - return metadataByVersion[version]; - } + private static void ReimportPlugin(string path) { + File.Delete(path + ".meta"); + AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate); } /// - /// Stores current and obsolete file references for a package. + /// Enable the editor plugin at the specified path. /// - public class ManifestReferences { - /// - /// Name of this package. - /// - public string filenameCanonical = null; - - /// - /// Metadata which references the most recent version metadata file. - /// - public FileMetadata currentMetadata = null; - - /// - /// Set of current files in this package. - /// - public HashSet currentFiles = new HashSet(); - - /// - /// Set of obsolete files in this package. - /// - public HashSet obsoleteFiles = new HashSet(); - - /// - /// Create an instance. - /// - public ManifestReferences() { } - - /// - /// Parse current and obsolete file references from a package's - /// manifest files. - /// - /// Metadata for files ordered by - /// version number. If the metadata does not have the isManifest - /// attribute it is ignored. - /// Set of all metadata files in the - /// project. This is used to handle file renaming in the parsed - /// manifest. If the manifest contains files that have been - /// renamed it's updated with the new filenames. - /// true if data was parsed from the specified file metadata, - /// false otherwise. - public bool ParseManifests(FileMetadataByVersion metadataByVersion, - FileMetadataSet metadataSet) { - currentFiles = new HashSet(); - obsoleteFiles = new HashSet(); - - int versionIndex = 0; - int numberOfVersions = metadataByVersion.Values.Count; - foreach (FileMetadata metadata in metadataByVersion.Values) { - versionIndex++; - if (!metadata.isManifest) return false; - bool manifestNeedsUpdate = false; - HashSet filesInManifest = - versionIndex < numberOfVersions ? - obsoleteFiles : currentFiles; - StreamReader manifestFile = - new StreamReader(metadata.filename); - string line; - while ((line = manifestFile.ReadLine()) != null) { - var manifestFileMetadata = new FileMetadata(line.Trim()); - string filename = manifestFileMetadata.filename; - // Check for a renamed file. - var existingFileMetadata = - metadataSet.FindMetadata( - manifestFileMetadata.filenameCanonical, - manifestFileMetadata.CalculateVersion()); - if (existingFileMetadata != null && - !manifestFileMetadata.filename.Equals( - existingFileMetadata.filename)) { - filename = existingFileMetadata.filename; - manifestNeedsUpdate = true; - } - filesInManifest.Add(filename); - } - manifestFile.Close(); - - // If this is the most recent manifest version, remove all - // current files from the set to delete. - if (versionIndex == numberOfVersions) { - currentMetadata = metadata; - foreach (var currentFile in filesInManifest) { - obsoleteFiles.Remove(currentFile); - } - } - - // Rewrite the manifest to track renamed files. - if (manifestNeedsUpdate) { - File.Delete(metadata.filename); - var writer = new StreamWriter(metadata.filename); - foreach (var filename in filesInManifest) { - writer.WriteLine(filename); - } - writer.Close(); - } - } - this.filenameCanonical = metadataByVersion.filenameCanonical; - return true; - } - - /// - /// Find and read all package manifests. - /// - /// Set to query for manifest files. - /// List of ManifestReferences which contain current and - /// obsolete files referenced in each manifest file. - public static List FindAndReadManifests( - FileMetadataSet metadataSet) { - var manifestReferencesList = new List(); - foreach (var metadataByVersion in metadataSet.Values) { - ManifestReferences manifestReferences = - new ManifestReferences(); - if (manifestReferences.ParseManifests(metadataByVersion, - metadataSet)) { - manifestReferencesList.Add(manifestReferences); - } - } - return manifestReferencesList; - } + private static void EnableEditorPlugin(string path) { + PluginImporter importer = AssetImporter.GetAtPath(path) as PluginImporter; + if (importer == null) { + UnityEngine.Debug.Log(String.Format("Failed to enable editor plugin {0}", path)); + return; + } + importer.SetCompatibleWithEditor(true); + AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate); } /// - /// Set of obsolete filenames. + /// Get a property by name. /// - public class ObsoleteFiles { - - /// - /// Obsolete files that are not referenced by any manifests. - /// - public HashSet unreferenced; - - /// - /// Obsolete files that are referenced by manifests. Each item in - /// the dictionary contains a list of manifests referencing the file. - /// - public Dictionary> referenced; - - /// - /// Build an ObsoleteFiles instance searching a set of - /// ManifestReferences and a FileMetadataSet for old files. - /// Old files are bundled into unreferenced (i.e not referenced by a - /// manifest that is not pending deletion) and reference (referenced - /// by an active manifest). - /// - /// List of manifests to query - /// for obsolete files. - /// Set of metadata to query for obsolete - /// files. - /// ObsoleteFiles instance which references the discovered - /// obsolete files. - public ObsoleteFiles( - List manifestReferencesList, - FileMetadataSet metadataSet) { - // Combine all currently referenced and obsolete files into a - // global sets. - var currentFiles = new HashSet(); - var obsoleteFiles = new HashSet(); - foreach (var manifestReferences in manifestReferencesList) { - currentFiles.UnionWith(manifestReferences.currentFiles); - obsoleteFiles.UnionWith(manifestReferences.obsoleteFiles); - } - // Fold in obsolete files that are not referenced by manifests. - foreach (var metadataByVersion in metadataSet.Values) { - obsoleteFiles.UnionWith( - metadataByVersion.FindObsoleteVersions()); - } - // Filter the obsoleteFiles set for all obsolete files currently - // in use and add to a dictionary indexed by filename - // which contains a list of manifest filenames which reference - // each file. - var referencedObsoleteFiles = - new Dictionary>(); - var obsoleteFilesToDelete = new HashSet(); - foreach (var obsoleteFile in obsoleteFiles) { - var manifestsReferencingFile = new List(); - foreach (var manifestReferences in manifestReferencesList) { - if (manifestReferences.currentFiles.Contains( - obsoleteFile)) { - manifestsReferencingFile.Add( - manifestReferences.currentMetadata.filename); - } - } - // If the referenced file doesn't exist, ignore it. - if (!File.Exists(obsoleteFile)) { - continue; - } - if (manifestsReferencingFile.Count > 0) { - referencedObsoleteFiles[obsoleteFile] = - manifestsReferencingFile; - } else { - obsoleteFilesToDelete.Add(obsoleteFile); - } - } - unreferenced = obsoleteFilesToDelete; - referenced = referencedObsoleteFiles; - } + private static PropertyInfo GetPropertyByName(string propertyName) { + var cls = BootStrappedImpl; + if (cls == null) return null; + return cls.GetProperty(propertyName); } - // Keys in the editor preferences which control the behavior of this - // module. - private const string PREFERENCE_ENABLED = - "Google.VersionHandler.VersionHandlingEnabled"; - private const string PREFERENCE_CLEANUP_PROMPT_ENABLED = - "Google.VersionHandler.CleanUpPromptEnabled"; - private const string PREFERENCE_VERBOSE_LOGGING_ENABLED = - "Google.VersionHandler.VerboseLoggingEnabled"; - - // Name of this plugin. - private const string PLUGIN_NAME = "Google Version Handler"; + /// + /// Get a boolean property's value by name. + /// + private static T GetPropertyByName(string propertyName, T defaultValue) { + var prop = GetPropertyByName(propertyName); + if (prop == null) return defaultValue; + return (T)prop.GetValue(null, null); + } /// - /// Enables / disables assets imported at multiple revisions / versions. - /// In addition, this module will read text files matching _manifest_ - /// and remove files from older manifest files. + /// Set a boolean property's value by name, /// - static VersionHandler() { - UpdateVersionedAssets(); + private static void SetPropertyByName(string propertyName, T value) { + var prop = GetPropertyByName(propertyName); + if (prop == null) return; + prop.SetValue(null, value, null); } /// /// Enable / disable automated version handling. /// public static bool Enabled { - get { - return !System.Environment.CommandLine.Contains("-batchmode") && - EditorPrefs.GetBool(PREFERENCE_ENABLED, defaultValue: true); - } - set { EditorPrefs.SetBool(PREFERENCE_ENABLED, value); } + get { return GetPropertyByName("Enabled", false); } + set { SetPropertyByName("Enabled", value); } } /// /// Enable / disable prompting the user on clean up. /// public static bool CleanUpPromptEnabled { - get { return EditorPrefs.GetBool(PREFERENCE_CLEANUP_PROMPT_ENABLED, - defaultValue: true); } - set { EditorPrefs.SetBool(PREFERENCE_CLEANUP_PROMPT_ENABLED, value); } + get { return GetPropertyByName("CleanUpPromptEnabled", false); } + set { SetPropertyByName("CleanUpPromptEnabled", value); } + } + + /// + /// Enable / disable renaming to canonical filenames. + /// + public static bool RenameToCanonicalFilenames { + get { return GetPropertyByName("RenameToCanonicalFilenames", false); } + set { SetPropertyByName("RenameToCanonicalFilenames", value); } } /// /// Enable / disable verbose logging. /// public static bool VerboseLoggingEnabled { - get { return System.Environment.CommandLine.Contains("-batchmode") || - EditorPrefs.GetBool(PREFERENCE_VERBOSE_LOGGING_ENABLED, - defaultValue: false); } - set { EditorPrefs.SetBool(PREFERENCE_VERBOSE_LOGGING_ENABLED, value); } + get { return GetPropertyByName("VerboseLoggingEnabled", false); } + set { SetPropertyByName("VerboseLoggingEnabled", value); } + } + + /// + /// Set the methods to call when the VersionHandler has finished updating. + /// Each string in the specified list should have the format + /// "assemblyname:classname:methodname". + /// assemblyname can be empty to search all assemblies for classname. + /// For example: + /// ":MyClass:MyMethod" + /// Would call MyClass.MyMethod() when the update process is complete. + /// + public static IEnumerable UpdateCompleteMethods { + get { + return GetPropertyByName>("UpdateCompleteMethods", + UpdateCompleteMethodsInternal); + } + + set { + if (Impl != null) { + SetPropertyByName("UpdateCompleteMethods", value); + } else { + UpdateCompleteMethodsInternal = value; + } + } + } + + // Backing store for update methods until the VersionHandler is boot strapped. + private static IEnumerable UpdateCompleteMethodsInternal { + get { + if (File.Exists(CALLBACKS_PATH)) { + return File.ReadAllText(CALLBACKS_PATH).Split(new [] { '\n' }); + } else { + return new List(); + } + } + + set { + File.WriteAllText( + CALLBACKS_PATH, + value == null ? "" : String.Join("\n", new List(value).ToArray())); + } } /// - /// Add the settings dialog for this module to the menu and show the - /// window when the menu item is selected. + /// Show the settings menu. /// - [MenuItem("Assets/Play Services Resolver/Version Handler/Settings")] public static void ShowSettings() { - SettingsDialog window = (SettingsDialog)EditorWindow.GetWindow( - typeof(SettingsDialog), true, PLUGIN_NAME + " Settings"); - window.Initialize(); - window.Show(); + InvokeImplMethod("ShowSettings"); } /// - /// Menu item which forces version handler execution. + /// Force version handler execution. /// - [MenuItem("Assets/Play Services Resolver/Version Handler/Update")] public static void UpdateNow() { - UpdateVersionedAssets(forceUpdate: true); - EditorUtility.DisplayDialog(PLUGIN_NAME, "Update complete.", "OK"); + InvokeImplMethod("UpdateNow", schedule: true); } /// @@ -1096,6 +345,11 @@ public static void UpdateNow() { /// Name of the file / directory to filter. public delegate bool FilenameFilter(string filename); + // Cast an object to a string array if it's not null, or return an empty string array. + private static string[] StringArrayFromObject(object obj) { + return obj != null ? (string[])obj : new string[] {}; + } + /// /// Search the asset database for all files matching the specified filter. /// @@ -1105,40 +359,24 @@ public static void UpdateNow() { /// /// 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) { - HashSet matchingEntries = new HashSet(); - assetsFilter = assetsFilter != null ? assetsFilter : "t:Object"; - foreach (string assetGuid in AssetDatabase.FindAssets(assetsFilter)) { - string filename = AssetDatabase.GUIDToAssetPath(assetGuid); - if (filter == null || filter(filename)) { - matchingEntries.Add(filename); - } - } - string[] entries = new string[matchingEntries.Count]; - matchingEntries.CopyTo(entries); - return entries; + FilenameFilter filter = null, + IEnumerable directories = null) { + return StringArrayFromObject(InvokeImplMethod("SearchAssetDatabase", null, + namedArgs: new Dictionary { + { "assetsFilter", assetsFilter }, + { "filter", filter }, + { "directories", directories }, + })); } /// /// Get all assets managed by this module. /// public static string[] FindAllAssets() { - return SearchAssetDatabase( - assetsFilter: "l:" + FileMetadata.ASSET_LABEL); - } - - /// - /// Move an asset to trash, writing to the log if logging is enabled. - /// - private static void MoveAssetToTrash(string filename) { - if (VerboseLoggingEnabled) { - UnityEngine.Debug.Log("Moved obsolete file to trash: " + filename); - } - if (!AssetDatabase.MoveAssetToTrash(filename)) { - UnityEngine.Debug.LogError( - "Failed to move obsolete file to trash: " + filename); - } + return StringArrayFromObject(InvokeImplMethod("FindAllAssets")); } /// @@ -1148,98 +386,78 @@ private static void MoveAssetToTrash(string filename) { /// are not present in the most recent manifests. /// public static void UpdateVersionedAssets(bool forceUpdate = false) { - // If this module is disabled do nothing. - if (!forceUpdate && !Enabled) return; - - var metadataSet = FileMetadataSet.FindWithPendingUpdates( - FileMetadataSet.ParseFromFilenames(FindAllAssets())); - - if (metadataSet.EnableMostRecentPlugins()) { - AssetDatabase.Refresh(); - } - - var obsoleteFiles = new ObsoleteFiles( - ManifestReferences.FindAndReadManifests(metadataSet), metadataSet); + InvokeImplMethod("UpdateVersionedAssets", + args: new object[] { forceUpdate }, + schedule: true); + } - // Obsolete files that are no longer reference can be safely - // deleted, prompt the user for confirmation if they have the option - // enabled. - bool deleteFiles = true; - if (obsoleteFiles.unreferenced.Count > 0) { - if (CleanUpPromptEnabled && deleteFiles) { - deleteFiles = EditorUtility.DisplayDialog( - PLUGIN_NAME, - "Would you like to delete the following obsolete files " + - "in your project?\n\n" + - String.Join("\n", new List( - obsoleteFiles.unreferenced).ToArray()), - "Yes", cancel: "No"); - } - foreach (var filename in obsoleteFiles.unreferenced) { - if (deleteFiles) { - MoveAssetToTrash(filename); - } else if (VerboseLoggingEnabled) { - UnityEngine.Debug.Log("Leaving obsolete file: " + - filename); - } - } + private static float unityVersionMajorMinor = -1.0f; + // Returns the major/minor version of the unity environment we are running in + // as a float so it can be compared numerically. + public static float GetUnityVersionMajorMinor() { + if (unityVersionMajorMinor > 0.0f) return unityVersionMajorMinor; + try { + var version = InvokeImplMethod("GetUnityVersionMajorMinor"); + unityVersionMajorMinor = (float)version; + return unityVersionMajorMinor; + } catch (Exception) { + return 0.0f; } + } - // If any obsolete referenced files are present, prompt the user for - // confirmation of deletion. - if (obsoleteFiles.referenced.Count > 0) { - List referencesString = new List(); - foreach (var item in obsoleteFiles.referenced) { - List lines = new List(); - lines.Add(item.Key); - foreach (var reference in item.Value) { - lines.Add(" " + reference); - } - referencesString.Add(String.Join("\n", lines.ToArray())); - } - deleteFiles = EditorUtility.DisplayDialog( - PLUGIN_NAME, - "The following obsolete files are referenced by packages in " + - "your project, would you like to delete them?\n\n" + - String.Join("\n", referencesString.ToArray()), - "Yes", cancel: "No"); - - foreach (var item in obsoleteFiles.referenced) { - if (deleteFiles) { - MoveAssetToTrash(item.Key); - } else if (VerboseLoggingEnabled) { - UnityEngine.Debug.Log( - "Leaving obsolete file: " + item.Key + " | " + - "Referenced by (" + - String.Join(", ", item.Value.ToArray()) + ")"); - } + /// Call a static method on a type returning null if type is null. + private static object InvokeImplMethod(string methodName, object[] args = null, + Dictionary namedArgs = null, + bool schedule = false) { + var type = BootStrappedImpl; + if (type == null) { + if (BootStrapping && schedule) { + // Try scheduling execution until the implementation is loaded. + AddToBootStrappingFile(new List { methodName }); } + return null; } + return InvokeStaticMethod(type, methodName, args, namedArgs: namedArgs); } - /// - /// Scanned for versioned assets and apply modifications if required. - /// - private static void OnPostProcessAllAssets ( - string[] importedAssets, string[] deletedAssets, - string[] movedAssets, string[] movedFromPath) { - UpdateVersionedAssets(); - } - - /// /// Find a class from an assembly by name. /// - /// Name of the assembly to search for. + /// Name of the assembly to search for. If this is null or empty, + /// the first class matching the specified name in all assemblies is returned. /// Name of the class to find. /// The Type of the class if found, null otherwise. public static Type FindClass(string assemblyName, string className) { - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { - if (assembly.GetName().Name == assemblyName) { - return Type.GetType(className + ", " + assembly.FullName); + Type type; + bool hasAssemblyName = !String.IsNullOrEmpty(assemblyName); + string fullName = hasAssemblyName ? className + ", " + assemblyName : className; + if (typeCache.TryGetValue(fullName, out type)) { + return type; + } + type = Type.GetType(fullName); + if (type == null) { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { + if (hasAssemblyName) { + if (assembly.GetName().Name == assemblyName) { + type = Type.GetType(className + ", " + assembly.FullName); + break; + } + } else { + // Search for the first instance of a class matching this name in all + // assemblies. + foreach (var currentType in assembly.GetTypes()) { + if (currentType.FullName == className) { + type = currentType; + break; + } + } + if (type != null) break; + } } + } - return null; + if (type != null) typeCache[fullName] = type; + return type; } /// @@ -1247,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( @@ -1263,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( @@ -1279,34 +497,150 @@ 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( Type type, object objectInstance, string methodName, object[] args, Dictionary namedArgs = null) { - MethodInfo method = type.GetMethod(methodName); - ParameterInfo[] parameters = method.GetParameters(); - int numParameters = parameters.Length; - object[] parameterValues = new object[numParameters]; - int numPositionalArgs = args != null ? args.Length : 0; - foreach (var parameter in parameters) { - int position = parameter.Position; - if (position < numPositionalArgs) { - parameterValues[position] = args[position]; - continue; - } - object namedValue = parameter.RawDefaultValue; - if (namedArgs != null) { - object overrideValue; - if (namedArgs.TryGetValue(parameter.Name, out overrideValue)) { - namedValue = overrideValue; + object[] parameterValues = null; + int numberOfPositionalArgs = args != null ? args.Length : 0; + int numberOfNamedArgs = namedArgs != null ? namedArgs.Count : 0; + MethodInfo foundMethod = null; + foreach (var method in type.GetMethods()) { + if (method.Name != methodName) continue; + var parameters = method.GetParameters(); + int numberOfParameters = parameters.Length; + parameterValues = new object[numberOfParameters]; + int matchedPositionalArgs = 0; + int matchedNamedArgs = 0; + bool matchedAllRequiredArgs = true; + foreach (var parameter in parameters) { + var parameterType = parameter.ParameterType; + int position = parameter.Position; + if (position < numberOfPositionalArgs) { + var positionalArg = args[position]; + // If the parameter type doesn't match, ignore this method. + if (positionalArg != null && + !parameterType.IsAssignableFrom(positionalArg.GetType())) { + break; + } + parameterValues[position] = positionalArg; + matchedPositionalArgs ++; + } else if (parameter.RawDefaultValue != DBNull.Value) { + object namedValue = parameter.RawDefaultValue; + if (numberOfNamedArgs > 0) { + object namedArg; + if (namedArgs.TryGetValue(parameter.Name, out namedArg)) { + // If the parameter type doesn't match, ignore this method. + if (namedArg != null && + !parameterType.IsAssignableFrom(namedArg.GetType())) { + break; + } + namedValue = namedArg; + matchedNamedArgs ++; + } + } + parameterValues[position] = namedValue; + } else { + matchedAllRequiredArgs = false; + break; } } - parameterValues[position] = namedValue; + // If all arguments were consumed by the method, we've found a match. + if (matchedAllRequiredArgs && + matchedPositionalArgs == numberOfPositionalArgs && + matchedNamedArgs == numberOfNamedArgs) { + foundMethod = method; + break; + } } - return method.Invoke(objectInstance, parameterValues); + if (foundMethod == null) { + throw new Exception(String.Format("Method {0}.{1} not found", type.Name, methodName)); + } + 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 +} // namespace Google diff --git a/source/VersionHandler/test/reflection/Assets/PlayServicesResolver/Editor/TestReflection.cs b/source/VersionHandler/test/reflection/Assets/PlayServicesResolver/Editor/TestReflection.cs new file mode 100644 index 00000000..abaffa31 --- /dev/null +++ b/source/VersionHandler/test/reflection/Assets/PlayServicesResolver/Editor/TestReflection.cs @@ -0,0 +1,334 @@ +// +// 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. +// + +using System; +using System.Collections.Generic; +using System.Collections; +using System.IO; + +using Google; + +// Class used to test method invocation. +public class Greeter { + private string name; + + public event Action instanceEvent; + + static public event Action staticEvent; + + public Greeter(string name) { + this.name = name; + } + + public static string GenericHello() { + return "Hello"; + } + + public static string GenericHelloWithCustomerName(string customerName) { + return String.Format("{0} {1}", GenericHello(), customerName); + } + + public static string GenericHelloWithCustomerName(int customerId) { + return String.Format("{0} customer #{1}", GenericHello(), customerId); + } + + public static string GenericHelloWithPronoun(string pronoun = "There") { + return String.Format("{0} {1}", GenericHello(), pronoun); + } + + public static string GenericHelloWithCustomerNameAndPronoun(string customerName, + string pronoun = "There") { + 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); + } + + public string Hello() { + return Greeter.GenericHello() + MyNameIs(); + } + + public string HelloWithCustomerName(string customerName) { + return Greeter.GenericHelloWithCustomerName(customerName) + MyNameIs(); + } + + public string HelloWithCustomerNameAndPronoun(string customerName, + string pronoun = "There") { + 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] +public class TestReflection { + + /// + /// Test all reflection methods. + /// + static TestReflection() { + // Disable stack traces for more condensed logs. + UnityEngine.Application.stackTraceLogType = UnityEngine.StackTraceLogType.None; + + // Run tests. + var failures = new List(); + foreach (var test in new Func[] { + TestFindClassWithAssemblyName, + TestFindClassWithoutAssemblyName, + TestInvokeStaticMethodWithNoArgs, + TestInvokeStaticMethodWithStringArg, + TestInvokeStaticMethodWithIntArg, + TestInvokeStaticMethodWithNamedArgDefault, + TestInvokeStaticMethodWithNamedArg, + TestInvokeStaticMethodWithArgAndNamedArgDefault, + TestInvokeStaticMethodWithArgAndNamedArg, + TestInvokeStaticMethodWithArgAndNamedInterfaceArg, + TestInvokeInstanceMethodWithNoArgs, + TestInvokeInstanceMethodWithNamedArgDefault, + TestInvokeInstanceMethodWithNamedArg, + TestInvokeStaticEventMethod, + }) { + var testName = test.Method.Name; + Exception exception = null; + bool succeeded = false; + try { + UnityEngine.Debug.Log(String.Format("Running test {0}", testName)); + succeeded = test(); + } catch (Exception ex) { + exception = ex; + succeeded = false; + } + if (succeeded) { + UnityEngine.Debug.Log(String.Format("{0}: PASSED", testName)); + } else { + UnityEngine.Debug.LogError(String.Format("{0} ({1}): FAILED", testName, exception)); + failures.Add(testName); + } + } + + if (failures.Count > 0) { + UnityEngine.Debug.Log("Test failed"); + foreach (var testName in failures) { + UnityEngine.Debug.Log(String.Format("{0}: FAILED", testName)); + } + UnityEditor.EditorApplication.Exit(1); + } + UnityEngine.Debug.Log("Test passed"); + UnityEditor.EditorApplication.Exit(0); + } + + // Test searching for a class when specifying the assembly name. + static bool TestFindClassWithAssemblyName() { + var expectedType = typeof(UnityEditor.EditorApplication); + var foundType = VersionHandler.FindClass("UnityEditor", "UnityEditor.EditorApplication"); + if (expectedType != foundType) { + UnityEngine.Debug.LogError(String.Format("Unexpected type {0} vs {1}", foundType, + expectedType)); + return false; + } + return true; + } + + // Test searching for a class without specifying the assembly name. + static bool TestFindClassWithoutAssemblyName() { + var expectedType = typeof(UnityEditor.EditorApplication); + var foundType = VersionHandler.FindClass(null, "UnityEditor.EditorApplication"); + if (expectedType != foundType) { + UnityEngine.Debug.LogError(String.Format("Unexpected type {0} vs {1}", foundType, + expectedType)); + return false; + } + return true; + } + + static bool CheckValue(string expected, string value) { + if (value != expected) { + UnityEngine.Debug.LogError(String.Format("Unexpected value {0} vs {1}", value, + expected)); + return false; + } + return true; + } + + // Invoke a static method with no arguments. + static bool TestInvokeStaticMethodWithNoArgs() { + return CheckValue("Hello", + (string)VersionHandler.InvokeStaticMethod(typeof(Greeter), "GenericHello", + null, null)); + } + + // Invoke an overloaded static method with a string arg. + static bool TestInvokeStaticMethodWithStringArg() { + return CheckValue("Hello Jane", + (string)VersionHandler.InvokeStaticMethod(typeof(Greeter), + "GenericHelloWithCustomerName", + new object[] { "Jane" }, null)); + } + + // Invoke an overloaded static method with an int arg. + static bool TestInvokeStaticMethodWithIntArg() { + return CheckValue("Hello customer #1337", + (string)VersionHandler.InvokeStaticMethod(typeof(Greeter), + "GenericHelloWithCustomerName", + new object[] { 1337 }, null)); + } + + // Invoke a static method with a default value of a named arg. + static bool TestInvokeStaticMethodWithNamedArgDefault() { + return CheckValue("Hello There", + (string)VersionHandler.InvokeStaticMethod(typeof(Greeter), + "GenericHelloWithPronoun", + null, null)); + } + + // Invoke a static method with a named arg. + static bool TestInvokeStaticMethodWithNamedArg() { + return CheckValue("Hello Miss", + (string)VersionHandler.InvokeStaticMethod( + typeof(Greeter), "GenericHelloWithPronoun", + null, new Dictionary { { "pronoun", "Miss" } })); + } + + // Invoke a static method with a positional and default value for a named arg. + static bool TestInvokeStaticMethodWithArgAndNamedArgDefault() { + return CheckValue("Hello There Bob", + (string)VersionHandler.InvokeStaticMethod( + typeof(Greeter), "GenericHelloWithCustomerNameAndPronoun", + new object[] { "Bob" }, null)); + } + + // Invoke a static method with a positional and named arg. + static bool TestInvokeStaticMethodWithArgAndNamedArg() { + return CheckValue("Hello Mrs Smith", + (string)VersionHandler.InvokeStaticMethod( + typeof(Greeter), "GenericHelloWithCustomerNameAndPronoun", + new object[] { "Smith" }, + 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", + (string)VersionHandler.InvokeInstanceMethod(new Greeter("Sam"), "Hello", + null, null)); + } + + // Invoke an instance method with an default value for a named argument. + static bool TestInvokeInstanceMethodWithNamedArgDefault() { + return CheckValue("Hello There Helen, my name is Sam", + (string)VersionHandler.InvokeInstanceMethod( + new Greeter("Sam"), "HelloWithCustomerNameAndPronoun", + new object[] { "Helen" }, null)); + } + + // Invoke an instance method with a named argument. + static bool TestInvokeInstanceMethodWithNamedArg() { + return CheckValue("Hello Mrs Smith, my name is Sam", + (string)VersionHandler.InvokeInstanceMethod( + new Greeter("Sam"), "HelloWithCustomerNameAndPronoun", + 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 new file mode 100644 index 00000000..ce756cd2 --- /dev/null +++ b/source/VersionHandlerImpl/Properties/AssemblyInfo.cs @@ -0,0 +1,62 @@ +// +// 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. +// + +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.VersionHandlerImpl")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Google Inc.")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Copyright 2016")] +[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 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. +// [assembly: AssemblyKeyFile("")] + +// 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/PlayServicesResolver/PlayServicesResolver.csproj b/source/VersionHandlerImpl/VersionHandlerImpl.csproj similarity index 61% rename from source/PlayServicesResolver/PlayServicesResolver.csproj rename to source/VersionHandlerImpl/VersionHandlerImpl.csproj index 31a6059a..d93cb5a4 100644 --- a/source/PlayServicesResolver/PlayServicesResolver.csproj +++ b/source/VersionHandlerImpl/VersionHandlerImpl.csproj @@ -3,12 +3,12 @@ Debug AnyCPU - 12.0.0 + 8.0.30703 2.0 - {82EEDFBE-AFE4-4DEF-99D9-BC929747DD9A} + {1E162334-8EA2-440A-9B3A-13FD8FE5C22E} Library - PlayServicesResolver - Google.JarResolver + Google + Google.VersionHandlerImpl 1.2 v3.5 @@ -23,7 +23,8 @@ False - none + True + full True bin\Release DEBUG;UNITY_EDITOR @@ -43,32 +44,37 @@ + - - - - - - - + + + + + + + + + + - - - Google.JarResolver\Dependency.cs - - - Google.JarResolver\PlayServicesSupport.cs - - - Google.JarResolver\ResolutionException.cs - + + + + + + + + {5378B37A-887E-49ED-A8AE-42FA843AA9DC} + VersionHandler + + 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